Está en la página 1de 212

Fundamentos de la Programación Orientada a

Objetos
Una Aplicación a las Estructuras de Datos en
Java TM

Ricardo Ruiz Rodrı́guez


Universidad Tecnológica de la Mixteca
Instituto de Computación
A Thelma
Ruiz Rodríguez, Ricardo

Fundamentos de la programación orientada a objetos: una aplicación a las estructuras de


datos en Java - 1º ed. - El Cid Editor, 2014.

pdf

ISBN digital – pdf 978-1-4135-2433-8

Fecha de catalogación: 18/02/2014

© Ricardo Ruiz Rodríguez


© El Cid Editor

ISBN versión digital pdf: 978-1-4135-2433-8


Índice general

Índice de figuras ix

Índice de ejemplos xiii

Prefacio xvii

1. Orientación a Objetos 1
1.1. Orı́genes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Paradigma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2.1. Una perspectiva diferente . . . . . . . . . . . . . . . . 4
1.2.2. Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.3. Objetos y clases . . . . . . . . . . . . . . . . . . . . . . 6
1.3. Orientación a objetos y modularidad . . . . . . . . . . . . . . 8
1.3.1. Cohesión y Acoplamiento . . . . . . . . . . . . . . . . 9
1.4. Caracterı́sticas fundamentales de la POO . . . . . . . . . . . . 9
1.5. Consideraciones finales . . . . . . . . . . . . . . . . . . . . . . 12
1.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2. Programación Orientada a Objetos 15


2.1. Mensajes y métodos . . . . . . . . . . . . . . . . . . . . . . . 16
2.1.1. Métodos sin argumentos . . . . . . . . . . . . . . . . . 16
2.1.2. Métodos con argumentos . . . . . . . . . . . . . . . . . 17
2.1.3. Métodos y atributos . . . . . . . . . . . . . . . . . . . 19
2.1.4. Métodos y constructores . . . . . . . . . . . . . . . . . 21
2.1.5. Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.2. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2.1. Abstracción . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2.2. Implementación . . . . . . . . . . . . . . . . . . . . . . 28

v
ÍNDICE GENERAL

2.3. Consi deraciones finales . . . . . . . . . . . . . . . . . . . . . . 33


2.3.1. Respecto al envı́o de mensajes . . . . . . . . . . . . . . 33
2.3.2. Respecto a la sobrecarga de operadores . . . . . . . . . 33
2.3.3. Respecto al paradigma . . . . . . . . . . . . . . . . . . 33
2.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

3. Estructuras de datos 39
3.1. Panorama general . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.2. Tipos de datos y referencias . . . . . . . . . . . . . . . . . . . 40
3.3. Tipos de datos abstractos (ADT) . . . . . . . . . . . . . . . . 41
3.3.1. Especificación del ADT Racional . . . . . . . . . . . . 43
3.3.2. Implementación del ADT Racional . . . . . . . . . . . 44
3.4. Abstracción de estructuras de datos . . . . . . . . . . . . . . . 47
3.4.1. Clases autorreferidas . . . . . . . . . . . . . . . . . . . 48
3.4.2. Implementación . . . . . . . . . . . . . . . . . . . . . . 50
3.5. Consideraciones finales . . . . . . . . . . . . . . . . . . . . . . 50
3.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

4. Pilas 55
4.1. Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.1.1. Operaciones primitivas . . . . . . . . . . . . . . . . . . 56
4.2. Implementación . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.2.1. Pila primitiva . . . . . . . . . . . . . . . . . . . . . . . 57
4.2.2. Pila genérica . . . . . . . . . . . . . . . . . . . . . . . 63
4.3. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.3.1. Análisis básico de expresiones . . . . . . . . . . . . . . 66
4.3.2. Notación interfija, postfija y prefija . . . . . . . . . . . 69
4.3.3. Evaluación de expresiones . . . . . . . . . . . . . . . . 71
4.4. Consideraciones finales . . . . . . . . . . . . . . . . . . . . . . 72
4.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

5. Colas de espera 79
5.1. Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.1.1. Operaciones primitivas . . . . . . . . . . . . . . . . . . 80
5.1.2. Representación . . . . . . . . . . . . . . . . . . . . . . 81
5.2. Implementación . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.3. Colas de prioridad . . . . . . . . . . . . . . . . . . . . . . . . 86
5.3.1. Cola de prioridad ascendente . . . . . . . . . . . . . . . 88

vi
ÍNDICE GENERAL

5.3.2. Cola de prioridad descendente . . . . . . . . . . . . . . 94


5.4. Consideraciones finales . . . . . . . . . . . . . . . . . . . . . . 95
5.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

6. Listas enlazadas 103


6.1. Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.1.1. Operaciones primitivas . . . . . . . . . . . . . . . . . . 104
6.1.2. Representación . . . . . . . . . . . . . . . . . . . . . 103
. 105
6.2. Implementación . . . . . . . . . . . . . . . . . . . . . . . . . . 106
6.3. Herencia vs. composición . . . . . . . . . . . . . . . . . . . . . 110
6.3.1. Implementacion de una pila utilizando herencia . . . . 110
6.3.2. Implementacion de una pila utilizando composición . . 115
6.4. Listas circulares . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.4.1. El problema de Josephus . . . . . . . . . . . . . . . . . 119
6.5. Listas doblemente enlazadas . . . . . . . . . . . . . . . . . . . 119
6.5.1. Definición, primitivas y representación . . . . . . . . . 120
6.6. Consideraciones finales . . . . . . . . . . . . . . . . . . . . . . 122
6.7. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

7. Árb oles binarios 129


7.1. Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
7.1.1. Representación y conceptos . . . . . . . . . . . . . . . 129
7.1.2. Operaciones primitivas . . . . . . . . . . . . . . . . . . 131
7.2. Árbol binario de búsqueda (ABB) . . . . . . . . . . . . . . . . 134
7.2.1. Operaciones primitivas . . . . . . . . . . . . . . . . . . 135
7.2.2. Representación . . . . . . . . . . . . . . . . . . . . . . 136
7.2.3. Implementación . . . . . . . . . . . . . . . . . . . . . . 137
7.2.4. Eliminación . . . . . . . . . . . . . . . . . . . . . . . . 145
7.3. Árboles binarios balanceados (AVL ) . . . . . . . . . . . . . . . 147
7.3.1. Definición y conceptos . . . . . . . . . . . . . . . . . . 147
7.3.2. Rotación simple . . . . . . . . . . . . . . . . . . . . . . 148
7.3.3. Rotación doble . . . . . . . . . . . . . . . . . . . . . . 150
7.4. Consideraciones finales . . . . . . . . . . . . . . . . . . . . . . 153
7.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

A. Java 159
A.1. Orı́genes y caracterı́sticas . . . . . . . . . . . . . . . . . . . . . 160
A.2. Estructura general de una clase . . . . . . . . . . . . . . . . . 160

vii
ÍNDICE GENERAL

A.3. Bienvenid@ a Java . . . . . . . . . . . . . . . . . . . . . . . . 162


A.4. Compilación . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
A.5. Ejemplos selectos . . . . . . . . . . . . . . . . . . . . . . . . . 165
A.5.1. Lectura de datos . . . . . . . . . . . . . . . . . . . . . 165
A.5.2. Estructuras de control . . . . . . . . . . . . . . . . . . 167
A.5.3. Arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . 170
A.5.4. Argumentos en la lı́nea de comandos . . . . . . . . . . 171
A.5.5. Excepciones . . . . . . . . . . . . . . . . . . . . . . . . 172
A.5.6. Genéricos . . . . . . . . . . . . . . . . . . . . . . . . . 174
A.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

Bibliografı́a 179

Índice Analı́tico 181

Agradecimientos 187

Acerca del Autor 189

viii
Índice de figuras

1.1. Ilusión óptica del conejo-pato creada por Joseph Jastrow . . . 3


1.2. Jerarquı́a de clases . . . . . . . . . . . . . . . . . . . . . . . . 7

2.1. Salida del Ejemplo 2.2 . . . . . . . . . . . . . . . . . . . . . . 17


2.2. Salida del Ejemplo 2.4 . . . . . . . . . . . . . . . . . . . . . . 19
2.3. Salida del Ejemplo 2.6 . . . . . . . . . . . . . . . . . . . . . . 21
2.4. Salida del Ejemplo 2.8 . . . . . . . . . . . . . . . . . . . . . . 23
2.5. Salida del Ejemplo 2.10 . . . . . . . . . . . . . . . . . . . . . . 24
2.6. Diagrama de clases UML para la relación de herencia entre
Cientifico y Persona . . . . . . . . . . . . . . . . . . . . . . . 26
2.7. Salida del Ejemplo 2.13 . . . . . . . . . . . . . . . . . . . . . . 31

3.1. Salida de una ejecución del Ejemplo 3.2 al intentar crear un


número racional cuyo denominador sea cero . . . . . . . . . . 44
3.2. Salida de la ejecución del Ejemplo 3.2 . . . . . . . . . . . . . . 46
3.3. Representación de un objeto de la clase Nodo (Ejemplo 3.3) . 49
3.4. Secuencia de nodos generados por una clase autorreferida . . . 50
3.5. Representación de un objeto autorreferido . . . . . . . . . . . 54

4.1. Crecimiento y decrecimiento de una pila . . . . . . . . . . . . 56


4.2. Abstracción de una pila como una secuencia de nodos . . . . . 57
4.3. Inserción de elementos en la pila primitiva . . . . . . . . . . . 61
4.4. Eliminación de elementos de la pila primitiva . . . . . . . . . . 62
4.5. Diagrama de clases UML para la pila genérica . . . . . . . . . 73

5.1. Inserción y eliminación de elementos en una cola de espera . . 80


5.2. Abstracción de una cola de espera como una secuencia de nodos 81
5.3. Diagrama de clases UML para la cola de espera . . . . . . . . 81
5.4. Salida del Ejemplo 5.4 . . . . . . . . . . . . . . . . . . . . . . 87

ix
ÍNDICE DE FIGURAS

5.5. Diagrama de clases en UML para una cola de prioridad ascen-


dente con la redefinición del método elimina . . . . . . . . . . 88
5.6. Diagrama de clases en UML para una cola de prioridad ascen-
dente con la redefinición del método inserta . . . . . . . . . . 89
5.7. Salida del Ejemplo 5.6 . . . . . . . . . . . . . . . . . . . . . . 93
5.8. Diagrama de clases en UML para una cola de prioridad ascen-
dente con la redefinición del método elimina . . . . . . . . . . 94
5.9. Diagrama de clases en UML para una cola de prioridad ascen-
dente con la redefinición del método inserta . . . . . . . . . . 95
5.10. Abstracción y representación de Round robin . . . . . . . . . . 98
5.11. Abstracción de una estructura de datos compuesta . . . . . . . 102

6.1. Abstraccion de una lista enlazada como una secuencia de nodos105


6.2. Diagrama de clases UML para la lista enlazada . . . . . . . . 106
6.3. Salida del Ejemplo 5.4 . . . . . . . . . . . . . . . . . . . . . . 111
6.4. Diagrama de clases UML para la implementación de una pila
utilizando herencia y una lista lista enlazada . . . . . . . . . . 112
6.5. Salida del Ejemplo 6.4 . . . . . . . . . . . . . . . . . . . . . . 114
6.6. Diagrama de clases UML para la implementación de una pila
utilizando composición y una lista lista enlazada . . . . . . . . 115
6.7. Abstraccion de una lista enlazada circular como una secuencia
de nodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.8. Abstracció n de una lista doblemente enlazada . . . . . . . . . 121
6.9. Diagrama de clases UML para una lista doblemente enlazada . 121
6.10. Representación de Round robin por niveles de prioridad . . . . 128

7.1. Abstraccion de un árbol binario como una estructura de nodos 130


7.2. Diagrama de clases UML para un árbol binario de búsqueda . 137
7.3. Una posible salida para el Ejemplo 7.3 y el Ejemplo 7.5 . . . . 142
7.4. Diagrama de clases UML para un árbol binario de búsqueda
con eliminación . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.5. Caso 1: Rotación derecha [Wirth] . . . . . . . . . . . . . . . . 149
7.6. Ejemplo de aplicación de la rotación sencilla . . . . . . . . . . 150
7.7. Caso 3: Rotación doble izquierda derecha (adaptada de [Wirth])151
7.8. Ejemplo de aplicación de la rotación doble . . . . . . . . . . . 152
7.9. Representación de un ABB . . . . . . . . . . . . . . . . . . . . 154
7.10. Eliminación en un árbol AVL (adaptada de [Wirth]) . . . . . . 158

x
ÍNDICE DE FIGURAS

A.1. Salida del Ejemplo A.2 . . . . . . . . . . . . . . . . . . . . . . 163


A.2. Salida del Ejemplo A.4 . . . . . . . . . . . . . . . . . . . . . . 163
A.3. Salida del Ejemplo A.6 . . . . . . . . . . . . . . . . . . . . . . 167
A.4. Salida del Ejemplo A.7 . . . . . . . . . . . . . . . . . . . . . . 169
A.5. Salida de los Ejemplos A.8, A.9 y A.10 . . . . . . . . . . . . . 170
A.6. Salida del Ejemplo A.11 . . . . . . . . . . . . . . . . . . . . . 171
A.7. Salida del Ejemplo A.12 sin argumentos . . . . . . . . . . . . 172
A.8. Salida del Ejemplo A.12 con argumentos . . . . . . . . . . . . 172
A.9. Relación UML de la jerarquı́a de la clases Exception en Java . 173

xi
Índice de ejemplos

2.1. Definición de la clase Parvulo1 . . . . . . . . . . . . . . . . . . 16


2.2. Clase de prueba para la clase Parvulo1 . . . . . . . . . . . . . 16
2.3. Definición de la clase Parvulo2 . . . . . . . . . . . . . . . . . . 18
2.4. Clase de prueba para la clase Parvulo2 . . . . . . . . . . . . . 18
2.5. Definición de la clase Parvulo3 . . . . . . . . . . . . . . . . . . 19
2.6. Clase de prueba para la clase Parvulo3 . . . . . . . . . . . . . 20
2.7. Definición de la clase Parvulo4 . . . . . . . . . . . . . . . . . . 22
2.8. Clase de prueba para la clase Parvulo4 . . . . . . . . . . . . . 23
2.9. Definición de la clase Parvulo5 . . . . . . . . . . . . . . . . . . 24
2.10. Clase de prueba para la clase Parvulo5 . . . . . . . . . . . . . 24
2.11. Definición de la clase Persona . . . . . . . . . . . . . . . . . . 28
2.12. Definición de la clase Cientifico . . . . . . . . . . . . . . . . . 30
2.13. Clase de prueba para la herencia . . . . . . . . . . . . . . . . 32
3.1. Clase implementa la abstracción de un número racional . . . . 44
3.2. Clase de prueba para la clase Racional . . . . . . . . . . . . . 47
3.3. Clase autorreferida . . . . . . . . . . . . . . . . . . . . . . . . 49
4.1. Definición de la clase NodoPrimitivo . . . . . . . . . . . . . . 57
4.2. Definición de la clase PilaPrimitiva . . . . . . . . . . . . . . . 58
4.3. Definición de la clase ExcepcionEDVacia que se utiliza para la
implementación de todas las estructuras de datos . . . . . . . 60
4.4. Clase de prueba para la clase PilaPrimitiva . . . . . . . . . . . 61
4.5. Definición de la clase para un nodo genérico (NodoG) . . . . . 63
4.6. Definición de la clase Pila que permite almacenar objetos genéri-
cos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.7. Clase de prueba para la pila genérica . . . . . . . . . . . . . . 65
5.1. Nodo genérico utilizado en la cola de espera . . . . . . . . . . 82
5.2. Excepción utilizada en la cola de espera . . . . . . . . . . . . 83

xiii
ÍNDICE DE EJEMPLOS

5.3. Definición de la clase Cola que permite almacenar objetos


genéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.4. Clase de prueba para la cola de espera . . . . . . . . . . . . . 86
5.5. Clase que define una cola de prioridad ascendente sobre escri-
biendo el método inserta . . . . . . . . . . . . . . . . . . . . . 90
5.6. Clase de prueba para la cola de prioridad ascendente . . . . . 92
5.7. Clase que implementa la interfaz Comparable . . . . . . . . . . 99
6.1. Definición de la clase Lista que permite almacenar objetos
genéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
6.2. Clase de prueba para la clase Lista . . . . . . . . . . . . . . . 109
6.3. Definición de la clase PilaH que implementa una pila de obje-
tos genéricos utilizando herencia y una lista enlazada . . . . . 112
6.4. Clase de prueba para PilaH . . . . . . . . . . . . . . . . . . . 113
6.5. Definición de la clase PilaC que implementa una pila de obje-
tos genéricos utilizando composición y una lista enlazada . . . 116
6.6. Clase de prueba para PilaC . . . . . . . . . . . . . . . . . . . 117
7.1. Definición de la clase NodoABB que permite representar ob-
jetos (nodos) genéricos para un árbol binario de búsqueda . . 138
7.2. Definición de la clase ABBr que permite almacenar nodos (No-
doABB) en un árbol binario de búsqueda . . . . . . . . . . . . 139
7.3. Clase de prueba para el árbol binario de búsqueda (recursivo) 141
7.4. Definición de la clase ABB que permite almacenar nodos (No-
doABB) en un árbol binario de búsqueda . . . . . . . . . . . . 143
7.5. Clase de prueba para el árbol binario de búsqueda . . . . . . . 145
A.1. Estructura general de una clase en Java . . . . . . . . . . . . . 161
A.2. Primer programa en Java (versión 1.0) . . . . . . . . . . . . . 162
A.3. Primer programa en Java (versión 1.1) . . . . . . . . . . . . . 163
A.4. Primer programa en Java (versión 1.2) . . . . . . . . . . . . . 163
A.5. Primer programa en Java (versión 1.3) . . . . . . . . . . . . . 164
A.6. Lectura de datos desde la entrada estándar . . . . . . . . . . . 166
A.7. Uso de la estructura de selección if y de los operadores rela-
cionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
A.8. Estructura de repetición while . . . . . . . . . . . . . . . . . 169
A.9. Estructura de repetición do-while . . . . . . . . . . . . . . . 170
A.10.Estructura de repetición for . . . . . . . . . . . . . . . . . . . 170
A.11.Arreglo de enteros (int) . . . . . . . . . . . . . . . . . . . . . 171
A.12.Procesamiento de argumentos en la lı́nea de comandos . . . . 172
A.13.Definición de una excepción . . . . . . . . . . . . . . . . . . . 174

xiv
ÍNDICE DE EJEMPLOS

A.14.Uso de una colección ArrayList . . . . . . . . . . . . . . . . . 177

xv
Prefacio

Estimado lector, este libro tiene una orientación especı́fica. Está pensado
para un curso introductorio de programación orientada a objetos, en donde,
de manera preferente aunque de ninguna manera obligatoria, se haya tenido
un contacto previo con algún lenguaje de programación utilizando el enfo-
que estructurado; sin embargo, también es mi intención que el libro sea de
utilidad para aquellos lectores que se quieran iniciar en el mundo de la pro-
gramación y el paradigma orientado a objetos, sin ningún requisito previo de
programación.
El libro asume que el lector posee conocimientos básicos de algoritmos
y/o programación, ası́ como el funcionamiento de las estructuras de control
secuencial, de selección, y de repetición. Por otro lado, si bien es cierto que
para la comprensión del paradigma no es preciso dichos conocimientos (de
hecho podrı́an generar un vicio para un paradigma de programación orien-
tado a objetos más puro), sı́ lo son para la comprensión y el seguimiento
correspondiente de los programas de ejemplo.
Con todo, el libro proporciona un apéndice para apoyar al lector a través
de ejemplos selectos, tanto en la introducción del lenguaje de programación
utilizado, como en los conceptos fundamentales de la programación.
Al respecto, existe un debate acerca de si es mejor enseñar el paradig-
ma orientado a objetos sin antes tener un conocimiento de otro enfoque de
programación (como el estructurado por ejemplo), o si es mejor partir de la
programación estructurada para realizar una transición hacia la programa-
ción orientada a objetos. En mi opinión ambos enfoques tienen sus ventajas
y desventajas, y se podrı́a estar ante el sempiterno problema del huevo y la
gallina.
Java es un lenguaje de programación hı́brido, en el sentido de que no es
un lenguaje totalmente orientado a objetos como Smalltalk, y en ese sentido,
tiene estructuras de control y tipos de datos primitivos al estilo del lenguaje

xvii
PREFACIO

de programación C, el cual es el lenguaje por antonomasia para la progra-


macion estructurada y, dado que este libro utiliza a Java como lenguaje de
programación, aquellos lectores que conozcan el lenguaje C se sentirán fami-
liarizados rápidamente con Java, concentrándose entonces en la asimilación
del paradigma y en su aplicación.
Por otro lado, aquellos lectores que no conozcan el enfoque estructurado
estarı́an, al menos de primera instancia, sin la predisposición a cometer uno
de los vicios más comunes en la programación orientada a objetos, como lo es
el de utilizar Java por ejemplo, para escribir programas siguiendo un enfoque
estructurado. En éste sentido, resulta fundamental enfatizar desde ahora que
el uso de un lenguaje de programación orientado a objetos, no hace per se, ni
mucho menos garantiza, que los programas que se escriban en dicho lenguaje
sigan el modelo de programación orientado a objetos.
Como en muchas cosas, más que establecer qué es lo mejor y qué no
lo es, ya que se está ante una disyuntiva subjetiva, finalmente el beneficio
dependerá tanto de las intenciones del lector, como de su disposición hacia la
comprensión del paradigma orientado a objetos, ası́ como de que se entienda,
que la asimilación de un nuevo enfoque de programación no es excluyente de
otros, sino que la diversidad de enfoques de solución o formas de resolver un
problema, amplı́an el repertorio de conocimientos y capacidades intelectuales
en pro de ser progresiva y eventualmente, mejores programadores.
Para ilustrar y complementar de mejor manera tanto el diseño como los
conceptos relacionados con los objetos, el libro se apoya de diagramas de
clase UML para su correspondiente representación, por lo que serı́a también
de mucha utilidad tener bases de UML, sin embargo, tampoco son indispen-
sables.
La intención de este libro es también la de introducir al lector en algunas
de las estructuras de datos más convencionales, y al mismo tiempo, utilizarlas
para ilustrar los conceptos del paradigma orientado a objetos a través de su
implementación. En éste sentido, la estructura general del libro, es la que se
describe a continuación:

Capı́tulo 1 presenta los elementos fundamentales de la orientación a obje-


tos. La intención del capı́tulo es la de proporcionar al lector un pano-
rama general del paradigma orientado a objetos sin asociarlo necesa-
riamente con la programación, y mucho menos con algún lenguaje de
programación en particular.

xviii
Capı́tulo 2 describe las bases del paradigma orientado a objetos en el con-
texto de su aplicación a la programación, utilizando a Java como len-
guaje de programación.
La intención del capı́tulo es la de concretizar en un lenguaje de progra-
macion especı́fico, los conceptos más distintivos del paradigma orienta-
do a objetos, para que en los capı́tulos siguientes, se puedan aplicar al
desarrollo de las estructuras de datos, y mejorar ası́ tanto su compren-
sión, como la experiencia del lector de manera progresiva.

Capı́tulo 3 presenta un panorama general de las estructuras de datos, tipos


de datos y referencias en Java. Se aborda también un concepto central
tanto para el paradigma orientado a objetos, como para las estructuras
de datos: los tipos de datos abstractos (ADT). Ası́ mismo, se sientan
las bases para la implementación de estructuras de datos basadas en
clases de autorreferencia.

Capı́tulo 4 presenta la primera de las estructuras de datos estudiadas en


el libro. El capı́tulo comienza con la definición de las propiedades y
operaciones definidas para el ADT pila, para después presentar la im-
plementación de una pila utilizando un tipo de dato primitivo. Poste-
riormente, tomando como base dicha implementación, se generaliza el
concepto para mostrar la implementación de una pila cuyos elementos
son objetos genéricos. Finalmente, se refuerza la definición del ADT, y
se muestra la relación que existe entre la implementación y el diseño
con el correspondiente diagrama de clases en UML.

Capı́tulo 5 introduce al lector en el estudio de las estructuras de datos de-


nominadas colas de espera. Se realiza una presentación de la cola de
espera ası́ como de su correspondiente implementación, para posterior-
mente analizar una variante de dicha estructura de datos: las colas de
prioridad. Ası́ mismo, el capı́tulo también presenta los elementos princi-
pales para la implementación de objetos con caracterı́sticas de relación
de orden a través de la interfaz Comparable del API de Java. Dicha
caracterı́stica resultará fundamental para los capı́tulos posteriores.

Capı́tulo 6 presenta al lector la última de las estructuras de datos linea-


les estudiadas en el libro: las listas enlazadas. Ası́ mismo, toda vez
que se asume completado el estudio de las pilas, el capı́tulo muestra
la posibilidad de la implementación de dicha estructura de datos por

xix
PREFACIO

medio de una lista enlazada. Adicionalmente, se comentan las ventajas


y desventajas de los enfoques de implementación utilizados.

Capı́tulo 7 introduce al lector tanto a los conceptos de árboles binarios,


como al conocimiento de algunas de las estructuras de datos de árboles
mas comunes: árboles binarios, árboles binarios de búsqueda (ABB), y
árboles AVL.
Los árboles binarios son estructuras de datos no lineales, y sus aplica-
ciones y usos son tan diversos, que están únicamente limitados por la
imaginación de quien los utiliza. El capı́tulo inicia presentando los con-
ceptos relacionados con los árboles binarios en general, y con los ABB
y AVL en particular. Presenta además dos tipos de implementación
para un ABB: con recursividad y con ciclos, y se describen también los
diferentes recorridos que es posible realizar con árboles binarios.

Apéndice A presenta un compendio de referencia a la mano de Java para


el lector familiarizado con algún lenguaje de programación. Al mismo
tiempo, es un marco de referencia respecto a caracterı́sticas que no se
tienen en un enfoque estructurado, como lo son las excepciones y los
genéricos por ejemplo. El apéndice presenta además una selección de
ejemplos de transición respecto a la forma de hacer las cosas en Java,
sin entrar en detalles especı́ficos respecto a la definición y descripción
de las estructuras de control, representación y notación de arreglos, etc.

Insisto en que este libro tiene una naturaleza introductoria, pero no por
ello informal. Confı́o plenamente en que puede servir como inicio de un largo
camino en la asimilación progresiva de los conceptos asociados con el para-
digma orientado a objetos. Espero sinceramente haber podido alcanzar la
meta de transmitir los conceptos fundamentales de la orientación a objetos,
ası́ como su aplicación en las estructuras de datos, utilizando al lenguaje de
programación Java como medio.

Ricardo Ruiz Rodrı́guez

xx
I consider to paradigms as universally recognized scientific
achievements that, for a time, provide model problems and
solutions for a community of researchers.

Thomas Samuel Kuhn

Programming is one of the most difficult branches of applied


mathematics; the poorer mathematicians had better remain pure
mathematicians.

Edsger Wybe Dijkstra

xxi
Capı́tulo 1

Orientación a Objetos

Technology is anything that wasn’t around when you were born.


Alan Curtis Kay
Hong Kong press conference

1.1. Orı́genes
Las caracterı́sticas principales de lo que actualmente se denomina Pro-
gramación Orientada a Objetos (POO) surgen en 1960, y aunque algunos
autores difieren en sus orı́genes, los conceptos de la POO tienen su inicio en
Simula 67, un lenguaje diseñado en el centro de cómputo noruego en Oslo1 .
Posteriormente, en Agosto de 1981, se publica en la revista Byte la des-
cripción del lenguaje de programación Smalltalk2 , el cual refinó algunos de
los conceptos originados con el lenguaje Simula.
Lo anterior dio pie a que en la década de 1980, los lenguajes de progra-
mación Orientados a Objetos (OO) tuvieran un rápido auge y expansión,
por lo que la POO se fue convirtiendo en el estilo de programación dominan-
te a mediados de los años ochenta del siglo XX, continuando vigente hasta
nuestros dı́as.
La POO es una de las propuestas de solución para ayudar a resolver
la denominada, aunque no generalmente aceptada, “crisis del software”. En
este sentido, es importante decir que, si bien las técnicas OO facilitan la
1
Lenguaje para simulaciones creado por Ole-Johan Dahl y Kristen Nygaard.
2
Desarrollado en Xerox PARC (Palo Alto-California Research Center ).

1
2 CAPÍTULO 1. ORIENTACIÓN A OBJETOS

creación de complejos sistemas de software por medio de mejores mecanismos


de abstracción, no son la panacea universal de solución.
Programar una computadora sigue siendo una de las tareas más difı́ci-
les jamás realizadas por un ser humano. Volverse experto en programación
requiere, no sólo de saber manejar herramientas y conocer técnicas de pro-
gramación, sino que es preciso contar también con:
Talento.

Creatividad.

Inteligencia.

Lógica.

Habilidad para construir y utilizar abstracciones.

Experiencia.
Por lo anterior, hacer un uso efectivo de los principios OO requiere de
una visión del mundo desde una perspectiva distinta, sobre todo si se parte
de la base de resolución de problemas a través de un enfoque estructurado.
Es importante señalar y tener presente desde este momento, que el uso de
un lenguaje de POO no hace, por sı́ mismo, que se programe OO, ya que se
podrı́a tener en el mejor de los casos, un programa o sistema implementado
con un enfoque estructurado, pero programado en un lenguaje orientado a
objetos.
La POO requiere de la comprensión del paradigma orientado a objetos.
La sección 1.2 discute el concepto de paradigma que se utilizará a lo largo
del texto.

1.2. Paradigma
El concepto de paradigma resulta fundamental en la comprensión del
paradigma orientado a objetos.
Antes de proporcionar la definición que se adoptará, describiré algunas
definiciones de paradigma que encontré:

paradigma m. Ejemplo o ejemplar: esa chica es el paradigma de la pacien-


cia.
1.2. PARADIGMA 3

Figura 1.1: Ilusión óptica del conejo-pato creada por Joseph Jastrow

paradigma ling. Cada uno de los esquemas formales a los que se ajustan las
palabras, según sus respectivas flexiones: paradigma de la conjugación
verbal.

paradigma ling. Conjunto de elementos de una misma clase gramatical que


pueden aparecer en un mismo contexto: paradigma de las preposiciones.

paradigma ejemplo o modelo. En todo el ámbito cientı́fico, religioso u otro


contexto epistemológico, el término paradigma puede indicar el concep-
to de esquema formal de organización, y ser utilizado como sinónimo
de marco teórico o conjunto de teorı́as.

Pero entonces, ¿qué entender por paradigma de programación?


La palabra paradigma irrumpió en el vocabulario moderno a través del
influyente libro “The Structure of Scientific Revolutions”del historiador de
la ciencia Thomas Samuel Kuhn[Kuhn].
Thomas Kuhn utilizó el término en la forma de la última definición: un
paradigma es un modelo para describir un conjunto de teorı́as, estándares y
métodos que en conjunto representan una forma de organizar el conocimiento,
esto es, una forma de ver el mundo.
En base a lo anterior se entederá como paradigma de programación
al modelo de programación utilizado, el cual está descrito y definido por un
conjunto de teorı́as, estándares y métodos que en conjunto, representan una
propuesta de solución por software hacia una problemática determinada.
4 CAPÍTULO 1. ORIENTACIÓN A OBJETOS

Kuhn utilizó la ilusión óptica de la Figura 1.1 para ilustrar el concepto


de paradigma. En dicha figura puede verse un conejo o un pato, dependiendo
de la perspectiva que se utilice.
El paradigma orientado a objetos cambió la perspectiva respecto del en-
foque estructurado, el cual era el paradigma dominante hasta entonces.

1.2.1. Una perspectiva diferente


El concepto sobre el que subyace la esencia de la orientación a objetos
es la abstracción. Por lo que, al pensar en este paradigma, deberá tener en
mente una perspectiva basada en los siguientes conceptos:
1. Entidades: agentes u objetos en interacción, donde cada objeto tiene
un rol.
2. Responsabilidades: cada objeto proporciona un conjunto de servicios
o lleva a cabo acciones que son utilizadas por otras entidades u objetos.
Las responsabilidades determinan el comportamiento del objeto.
3. Mensajes: en la POO la acción es iniciada por la transmisión de un
mensaje a un objeto responsable de dicha acción. En respuesta al men-
saje, el objeto receptor llevará a cabo un método para satisfacer la
solicitud que le fue realizada por dicho mensaje.
Los tres elementos anteriormente mencionados, constituyen los funda-
mentos primordiales de la orientación a objetos. A lo largo del texto se desa-
rrollarán de manera progresiva, y se ejemplificarán con programas.

Mensajes, procedimientos/funciones y métodos


Los mensajes son solicitudes especı́ficas de alguno de los servicios o res-
ponsabilidades asignadas a un objeto. Los mensajes tienen un receptor es-
pecı́fico, por lo que son enviados a un objeto en particular con una posible
lista de argumentos determinada.
Los mensajes son llevados a cabo por métodos, los cuales son algoritmos
asociado a un objeto (o a una clase de objetos), cuya ejecución se desencadena
tras la recepción de un mensaje. En este sentido, tanto los métodos como los
procedimientos o funciones son un conjunto de pasos bien definidos que llevan
a cabo una acción; sin embargo, los mensajes y los procedimientos o funciones
se distinguen esencialmente por dos aspectos:
1.2. PARADIGMA 5

1. En un mensaje, hay un receptor designado para dicho mensaje.

2. La interpretación o método utilizado para responder al mensaje es de-


terminado por el receptor, y puede variar en función del receptor.

1.2.2. Objetos
Los objetos son esencialmente abstracciones. Son entidades que tienen un
determinado estado, un comportamiento (determinado por sus responsabili-
dades), y una identidad.

El estado está representado por los datos o los valores que contienen los
atributos del objeto, los cuales son a su vez, otros objetos o variables
que representan las caracterı́sticas inherentes del objeto.

El comportamiento está determinado por las responsabilidades o servicios


del objeto, los cuales son definidos por los métodos, mismos que se
solicitan a través de mensajes a los que sabe responder dicho objeto.

La identidad es la propiedad que tiene un objeto que lo distingue de los


demás. La identidad está representada por un identificador.

Un objeto es una entidad que contiene en sı́ mismo, toda la información


necesaria, misma que permite definirlo, identificarlo, y accederlo3 frente a
otros objetos pertenecientes a otras clases, e incluso frente a objetos de su
misma clase.
Los objetos se valen de mecanismos de interacción llamados métodos,
que favorecen la comunicación entre ellos. Dicha comunicación favorece a su
vez el cambio de estado en los propios objetos. Esta caracterı́stica define a
los objetos como unidades indivisibles, en las que no se separa el estado del
comportamiento.

Orientación a objetos vs. enfoque estructurado


La orientación a objetos difiere del enfoque estructurado básicamente, en
que en la programación estructurada los datos y los procedimientos están
3
La forma de acceder a un objeto es a través de su interfaz. La interfaz de un objeto
es el conjunto de servicios públicos que ofrece el objeto, los cuales se solicitan a través de
mensajes o solicitudes realizadas a dicho objeto.
6 CAPÍTULO 1. ORIENTACIÓN A OBJETOS

separados y sin relación, ya que lo único que se busca en ésta última, es el


procesamiento de los datos de entrada para obtener los datos de salida.
La programación estructurada utiliza en primera instancia, un enfoque
basado en procedimientos o funciones, y en segunda instancia, las estructuras
de datos que dichos procedimientos o funciones manejan, cumpliendo ası́ la
Ecuación 1.1 planteada por Niklaus Wirth.

Algoritmos + Estructuras de Datos = P rogramas (1.1)


Por otro lado, un programa en un enfoque OO solicita estructuras de
datos (las cuales son otros objetos) para llevar a cabo un servicio.
La perspectiva OO también define programas compuestos por algoritmos
y estructuras de datos esencialmente (como los de la Ecuación 1.1), sin em-
bargo lo hace desde un enfoque diferente. En la orientación a objetos la des-
cripción del objeto se da en términos de responsabilidades y caracterı́sticas, y
al analizar un problema en dichos términos, se eleva el nivel de abstracción.
Lo anterior permite una mayor independencia entre los objetos, lo cual
es un factor crı́tico en la solución de problemas complejos. Cabe mencionar
por último, que al conjunto completo de responsabilidades asociadas con un
objeto es comúnmente referido como protocolo.

1.2.3. Objetos y clases


Todos los objetos son instancias de una clase (categorı́a). Esta relación
de un objeto con una clase, hace que los objetos tengan las siguientes carac-
terı́sticas:

El método invocado por un objeto en respuesta a un mensaje, es de-


terminado por la clase del objeto receptor.

Todos los objetos de una clase determinada, utilizan el mismo método


en respuesta a mensajes similares.

Las clases pueden ser organizadas en una estructura jerárquica de he-


rencia como la que se muestra en la Figura 1.2.

Una clase hija o subclase heredará todas las caracterı́sticas de la clase


de la que deriva (clase padre).
1.2. PARADIGMA 7

Figura 1.2: Jerarquı́a de clases

Una clase abstracta es una clase de la que no se derivan instancias


directamente, sino que es utilizada únicamente para crear subclases.

La búsqueda del método a invocar en respuesta a un mensaje deter-


minado, inicia en la clase del receptor. Si no se encuentra el método
apropiado, la búsqueda se realiza en la clase padre, y ası́ sucesivamente
hasta encontrar el método correspondiente.

Si se encuentran métodos con el mismo nombre dentro de la jerarquı́a


de clases, se dice que el método procesado sobre escribe (override) el
comportamiento heredado.

La Figura 1.2 presenta una posible jerarquı́a de clases para un Ser Vivo.
Mas que una clasificación o taxonomı́a completa, la figura muestra el concepto
de herencia a través de un árbol, el cual exhibe, que los elementos que se
derivan, comparten caracterı́sticas (atributos) y comportamientos (métodos)
semejantes.
Ası́ por ejemplo, es posible decir que Flipper es una instancia particular de
todos los posibles delfines que podrı́an existir. A su vez, un Delfı́n comparte
8 CAPÍTULO 1. ORIENTACIÓN A OBJETOS

caracterı́sticas comunes con una Ballena en cuanto a que ambos son Cetáceos,
pero difieren en otras4 .
Un Delfı́n es un Cetáceo, y un Cetáceo es un Mamı́fero. En muchas oca-
siones a éste tipo de relaciones se le denomina “es un”(is a), y es una carac-
terı́stica útil para identificar herencia, pero no es la única.
Existe otro tipo de relación y se denomina “tiene” (has-a). Estas relacio-
nes son las dos formas más importantes de abstracción en la orientación a
objetos:

1. La idea de división en partes (has-a): un automóvil tiene un motor,


tiene una transmisión, tiene un sistema eléctrico, etc.

2. La idea de división en especializaciones (is-a): un automóvil es un


medio de transporte, es un objeto de cuatro ruedas, es un objeto que
se dirige con un volante, etc.

Finalmente, la Figura 1.2 muestra que Flipper es un Delfı́n, que un Delfı́n


es un Cetáceo, y que éste a su vez es un Mamı́fero; que un Mamı́fero per-
tenece al Reino Animal, y que el Reino Animal es parte de los seres vivos
representados por la super clase o clase base Ser Vivo.

1.3. Orientación a objetos y modularidad


La modularidad no está exclusivamente relacionada con los procedimien-
tos o funciones de la programación estructurada, sino con el grado en el que
los componentes de un sistema pueden ser separados y reutilizados.
En base a lo anterior, tanto los métodos como los objetos son en sı́ mismos
módulos de una aplicación determinada, y en consecuencia, las clases de las
que se derivan constituyen los módulos del sistema, por lo que de aquı́ en
adelante se hará referencia a la modularidad de manera indistinta tanto para
clases, como para los métodos de las clases.
La modularidad ayuda también a hacer el código más comprensible, y ésto
a su vez hace que en consecuencia el código sea más fácil de mantener. Sin
embargo, sin las debidas consideraciones, la modularidad tiene también sus
consecuencias negativas, las cuales están en función directa de dos conceptos
4
Si un delfı́n y una ballena coincidieran en todo (caracterı́sticas y comportamiento)
serı́an de la misma clase.
1.4. CARACTERÍSTICAS FUNDAMENTALES DE LA POO 9

fundamentales en el desarrollo de software en general, y en el paradigma


orientado a objetos en particular:

1. Cohesión.

2. Acoplamiento.

1.3.1. Cohesión y Acoplamiento


La cohesión está relacionada con la integridad interna de un módulo. Es
el grado o nivel de relación o integridad entre los elementos que componen
un módulo.
El nivel de cohesión determina, qué tan fuerte están relacionados cada
unos de los elementos de funcionalidad expresados en el código fuente de un
módulo.
Por otro lado, el acoplamiento describe qué tan fuerte un módulo está re-
lacionado con otros, es decir, es el grado en que un módulo depende de cada
uno de los otros módulos que componen un sistema.
El acoplamiento también puede ser referido o entendido como dependen-
cia, lo cual ayuda a recordar que lo que se desea es mantener un bajo nivel
de dependencia entre los módulos, es decir un bajo acoplamiento.
En general, se desea que los módulos de un programa o sistema tengan,
un alto nivel de cohesión y un bajo nivel de acoplamiento, y el paradigma
orientado a objetos persigue y enfatiza dichos objetivos.

1.4. Caracterı́sticas fundamentales de la POO


Por último, pero no por ello menos importante, es importante mencionar
que Alan Curtis Kay, quien es considerado por muchos como el padre de la
POO, definió un conjunto de caracterı́sticas fundamentales para el paradig-
ma.
En base a lo propuesto por Kay, en la Programación Orientada a Objetos:

1. Todo es un objeto.

2. El procesamiento es llevado a cabo por objetos:


10 CAPÍTULO 1. ORIENTACIÓN A OBJETOS

Los objetos se comunican unos con otros solicitando que se lleven


a cabo determinadas acciones.
Los objetos se comunican enviando y recibiendo mensajes.
Un mensaje es la solicitud de una acción, la cual incluye los argu-
mentos que son necesarios para completar la tarea.

3. Cada objeto tiene su propia memoria, misma que está compuesta de


otros objetos.

4. Cada objeto es una instancia de una clase. Una clase representa un


grupo de objetos similares.

5. La clase es el repositorio del comportamiento asociado con un objeto.

Todos los objetos que son instancias de la misma clase, llevan a


cabo las mismas acciones.

6. Las clases están organizadas en una estructura jerárquica de árbol de-


nominada jerarquı́a de herencia.

La memoria y el comportamiento asociados con las instancias de


una clase, están automáticamente disponibles para cualquier clase
asociada con la descendencia dentro de la estructura jerárquica de
árbol.

Complementando la visión de Alan Kay, se presenta a continuación un


compendio de conceptos que definen también y refuerzan, las caracterı́sticas
principales de la POO:

La abstracción denota las caracterı́sticas esenciales de un objeto.


El proceso de abstracción permite seleccionar las caracterı́sticas rele-
vantes dentro de un conjunto, e identificar comportamientos comunes
para definir nuevos tipos de entidades.
La abstracción es la consideración aislada de las cualidades esenciales
de un objeto en su pura esencia o noción.

La modularidad es la propiedad que permite subdividir una aplicación en


partes más pequeñas (llamadas módulos), cada una de las cuales debe
1.4. CARACTERÍSTICAS FUNDAMENTALES DE LA POO 11

ser tan independiente como sea posible de la aplicación en sı́, y de las


partes restantes.
La modularidad es el grado en el que los componentes de un sistema
pueden ser separados y reutilizados.

El encapsulamiento tiene que ver con reunir todos los elementos que pue-
den considerarse pertenecientes a una misma entidad, al mismo nivel
de abstracción. Esto permite aumentar la cohesión de los módulos o
componentes del sistema.

El principio de ocultación de información (information hiding) se re-


fiere a que cada objeto está aislado del exterior, es un módulo indepen-
diente, y cada tipo de objeto presenta una interfaz a otros objetos, la
cual especifica cómo es que pueden interactuar con él.
El aislamiento protege a las propiedades de un objeto contra su mo-
dificación por quien no tenga derecho a acceder a ellas; solamente los
propios métodos internos del objeto pueden acceder a su estado. Ésto
asegura que otros objetos no puedan cambiar el estado interno de un
objeto de manera accidental o intencionada, eliminando efectos secun-
darios e interacciones inesperadas.

El polimorfismo está relacionado con el aspecto de que comportamientos


diferentes asociados a objetos distintos, pueden compartir el mismo
nombre.
El polimorfismo es la capacidad que tienen los objetos de naturaleza
heterogénea, de responder de manera diferente a un mismo mensaje, en
función de las caracterı́sticas y responsabilidades del objeto que recibe
dicho mensaje.

La herencia organiza y facilita el polimorfismo y el encapsulamiento, per-


mitiendo a los objetos ser definidos y creados como tipos especializados
de objetos preexistentes.
Las clases no están aisladas, sino que se relacionan entre sı́ formando
una jerarquı́a de clasificación.
Los objetos heredan las propiedades y el comportamiento de todas las
clases a las que pertenecen. Ası́, los objetos pueden compartir y exten-
der su comportamiento sin tener que volver a implementarlo.
12 CAPÍTULO 1. ORIENTACIÓN A OBJETOS

Cuando un objeto hereda de más de una clase se dice que hay herencia
múltiple.

Es importante tener todos estos conceptos presentes, estudiarlos, anali-


zarlos y comprenderlos, la memorización no es recomendable, ya que es bien
sabido que el memorizar conceptos no implica su comprensión, pero si un
concepto es comprendido, es posible explicarlo y deducir en consecuencia su
definición.
Mi deseo es que el lector reflexione sobre este aspecto y que, con un
poco de paciencia, sume a su repertorio de conocimientos el conjunto de
conceptos descritos hasta aquı́, los cuales se pondrán en práctica eventual y
progresivamente, en los capı́tulos siguientes.

1.5. Consideraciones finales


Este capı́tulo discutió los elementos fundamentales de la orientación a
objetos. La intención del capı́tulo es la de proporcionar al lector un panorama
general del paradigma orientado a objetos, sin asociarlo necesariamente con
la programación, y mucho menos con algún lenguaje de programación en
particular.
Un lenguaje de programación es sólo un medio para el paradigma, no el
paradigma en sı́. Por otro lado, el ejercicio de la programación es la forma
de aplicar los conceptos asociados al paradigma.
Los capı́tulos siguientes irán detallando al lector los conceptos presentados
hasta ahora, pero desde la perspectiva de la programación y su aplicación
en un lenguaje de programación. Sin embargo, es importante aclarar que
el objetivo del libro no es enseñar un lenguaje de programación, sino el de
utilizar al lenguaje como un medio para hacer tangibles los conceptos de
orientación a objetos.
El Apéndice A introduce algunos aspectos del lenguaje de programación
Java que podrı́an ser de utilidad para el lector, pero de ninguna manera
pretende cubrir los elementos completos de dicho lenguaje, sino solamente
presentar un panorama general.
1.6. EJERCICIOS 13

1.6. Ejercicios
1. Investigue más acerca de la historia y desarrollo de la programación
orientada a objetos.

2. Xerox es actualmente una compañı́a que desarrolla equipo de foto copia-


do e impresión entre otras cosas. Investigue cuál era el sistema operativo
que utilizaban, que por cierto, ya incluı́a una GUI (Interfaz Gráfica de
Usuario), pionera de las GUI que actualmente se utilizan.

3. Investigue la historia y la ideologı́a del lenguaje de programación Small-


talk. Aproveche para conocer el nombre de su(s) creador(es).

4. Investigue la historia y la ideologı́a del lenguaje de programación Eiffel.


Aproveche para conocer el nombre de su(s) creador(es).

5. Investigue la historia del lenguaje de programación C++. Aproveche


para conocer el nombre de su(s) creador(es).

6. Investigue el papel del Dr. Alan Curtis Kay en la concepción y desa-


rrollo de la programación orientada a objetos.

7. Investigue qué otros paradigmas de programación existen.

8. Investigue qué lenguajes de programación actuales soportan el paradig-


ma orientado a objetos, cuáles lo soportan de manera nativa y cuáles
como una extensión.
14 CAPÍTULO 1. ORIENTACIÓN A OBJETOS
Capı́tulo 2

Programación Orientada a
Objetos

Object-oriented programming is an exceptionally bad idea which


could only have originated in California.
Edsger Wybe Dijkstra (attributed to)

I don’t know how many of you have ever met Dijkstra, but you
probably know that arrogance in computer science is measured in
nano-Dijkstras.
Alan Curtis Kay

Este capı́tulo presenta las bases del paradigma orientado a objetos en


el contexto de su aplicación a la programación utilizando un lenguaje de
programación.
La intención principal del capı́tulo, es concretizar en un lenguaje de pro-
gramación los conceptos más distintivos del paradigma orientado a objetos,
para que en los capı́tulos subsecuentes, se puedan aplicar al desarrollo de las
estructuras de datos, y mejorar ası́ tanto la comprensión de los conceptos,
como la experiencia del lector.
Antes de continuar con este capı́tulo, no estarı́a de más que el lector
revisara el Apéndice A, a excepción de que se esté ampliamente familiarizado
con el lenguaje de programación Java.

15
16 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

2.1. Mensajes y métodos


Una de las principales inquietudes que expresan los estudiantes acerca
del paradigma orientado a objetos, está relacionada con los mensajes y los
métodos. En este sentido, se iniciará con un ejemplo sumamente sencillo, el
cual irá evolucionando progresivamente con la finalidad de ilustrar dichos
conceptos.

2.1.1. Métodos sin argumentos


El Ejemplo 2.1 muestra la definición de la clase Parvulo1, la cual no
contiene atributos, pero sı́ un método cuyo identificador o nombre es mensaje.
1 /∗ Ejemplo de e n v i o de mensaje e i n v o c a c i o n de metodos ( V e r si o n 1 . 0 ) .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s P a r v u l o 1 {
5 public void mensaje ( ) {
6 System . out . p r i n t l n ( ” Dentro d e l metodo mensaje ( ) ” ) ;
7 }
8 }

Ejemplo 2.1: Definición de la clase Parvulo1

El método mensaje tiene como única responsabilidad la impresión en la


salida estándar de una cadena1 .
En base a lo anterior, la clase Parvulo1 es una plantilla capaz de generar
objetos con una única responsabilidad o servicio, y no puede ser instanciada
ni ejecutada por sı́ misma. Para poder instanciar objetos de la clase Parvulo1
y poder visualizar su funcionamiento, se requiere de una clase de prueba
que permita generar una instancia de ella.
1 /∗ C l a s e de p r u e b a para P a r v u l o 1 . Se c r e a e l o b j e t o ” p a r v u l o ” i n s t a n c i a d o
2 de l a c l a s e P a r v u l o 1 y s e l e e n v i a e l mensaje ” mensaje ” .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s PruebaParvulo1 {
6 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
7 P a r v u l o 1 p a r v u l o = new P a r v u l o 1 ( ) ;
8
9 p a r v u l o . mensaje ( ) ;
10 }
11 }

Ejemplo 2.2: Clase de prueba para la clase Parvulo1


1
Los detalles generales del funcionamiento de println son presentados en la Sección A.3.
2.1. MENSAJES Y MÉTODOS 17

Figura 2.1: Salida del Ejemplo 2.2

La clase de prueba para el Ejemplo 2.1 es la clase PruebaParvulo1 que se


presenta en el Ejemplo 2.2.
La clase PruebaParvulo1 tiene la estructura de la mayorı́a de las clases
de prueba que se utilizarán en el texto, y está basada en la definición del
método main.
En la lı́nea 7 se define el objeto parvulo cuya clase (tipo) es Parvulo1 ;
ası́ mismo, observe que se genera una instancia por medio de la cláusula new,
el cual, entre otras cosas, construye el objeto. Si la clase Parvulo1 tuviera
un constructor explı́cito, serı́a precisamente aquı́ en donde se invocarı́a. Más
adelante en esta misma sección, se profundizará un poco más al respecto.
Una vez que el objeto existe (ha sido instanciado), es posible interactuar
con él por medio de mensajes para solicitarle acciones que correspondan con
las responsabilidades o servicios definidos para dicho objeto que, para el caso
del objeto parvulo, es sólo una.
La solicitud del único servicio que puede proporcionar el objeto parvulo se
realiza a través del mensaje mensaje, el cual es enviado (invocado) al objeto
en la lı́nea 9:
parvulo.mensaje();
La expresión anterior se interpreta como: “se envı́a el mensaje mensaje al
objeto parvulo”. El envı́o de mensajes no es otra cosa más que la invocación
explı́cita de un método a través de su identificador, es utilizar el método
correspondiente para realizar una acción, misma que está relacionada con el
comportamiento o las responsabilidades del objeto en cuestión.
La salida del Ejemplo 2.2 se muestra en la Figura 2.1. Asegúrese de com-
prender lo hasta aquı́ descrito antes de continuar.

2.1.2. Métodos con argumentos


En esta sección se presenta una versión ligeramente distinta del Ejemplo
2.1, en el cual se presentó el envı́o de mensajes sin argumentos. Tómese el
tiempo necesario para comparar el Ejemplo 2.3 de esta sección con el Ejemplo
2.1, y compruebe que son esencialmente iguales.
El argumento nombre en el método mensaje constituye la diferencia de los
18 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

ejemplos anteriormente mencionados. En el Ejemplo 2.3 el método mensaje


(lı́nea 5) define ahora la capacidad de recibir el argumento nombre, el cual
se define como un objeto de la clase String. El método mensaje imprime en
la salida estándar un cadena conformada por un texto predefinido (lı́nea 6)
y la cadena referida por nombre.
1 /∗ Ejemplo de e n v i o de mensaje e i n v o c a c i o n de metodos ( V e r si o n 1 . 1 ) .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s P a r v u l o 2 {
5 public void mensaje ( S t r i n g nombre ) {
6 System . out . p r i n t l n ( ”Mi nombre e s ” + nombre ) ;
7 }
8 }

Ejemplo 2.3: Definición de la clase Parvulo2

Por otro lado, el Ejemplo 2.4 muestra la clase de prueba para el Ejemplo
2.3, la cual es también similar a la del Ejemplo 2.2 excepto en la forma en
que se envı́a el mensaje al objeto parvulo (lı́nea 10 del Ejemplo 2.4). Observe
que el mensaje enviado tiene ahora una cadena como argumento, la cual es
referida por el objeto nombre del método mensaje (lı́nea 5 del Ejemplo 2.3)
en el momento en que se le envı́a el mensaje mensaje al objeto parvulo.

1 /∗ C l a s e de p r u e b a para P a r v u l o 2 . Se c r e a e l o b j e t o ” p a r v u l o ”
2 i n s t a n c i a d o de l a c l a s e P a r v u l o 2 y s e l e e n v i a e l mensaje
3 ” mensaje ” con un argumento .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 public c l a s s PruebaParvulo2 {
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 P a r v u l o 2 p a r v u l o = new P a r v u l o 2 ( ) ;
9
10 p a r v u l o . mensaje ( ” R i c a r d o ” ) ;
11 }
12 }

Ejemplo 2.4: Clase de prueba para la clase Parvulo2

Asegúrese de realizar una labor analı́tica al comparar lı́nea a lı́nea, tanto


las clases Parvulo1 y Parvulo2, como las clases PruebaParvulo1 y Prueba-
Parvulo2, ası́ como de comprender sus diferencias en base a lo que se ha
descrito hasta ahora.
La salida del Ejemplo 2.4 se muestra en la Figura 2.2.
2.1. MENSAJES Y MÉTODOS 19

Figura 2.2: Salida del Ejemplo 2.4

2.1.3. Métodos y atributos


Por el principio de ocultación de información, es conveniente que única-
mente se tenga acceso a los atributos de una clase a través de su interfaz. La
interfaz de un objeto está representada por sus métodos públicos (public).
1 /∗ Ejemplo de e n v i o de mensaje e i n v o c a c i o n de metodos ( V e r s io n 1 . 2 ) .
2 Se i n t r o d u c e n l o s metodos s e t y g e t para a t r i b u t o s de c l a s e .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s P a r v u l o 3 {
6 private S t r i n g nombre ;
7
8 public void e s t a b l e c e N o m b r e ( S t r i n g n ) {
9 nombre = n ;
10 }
11
12 public S t r i n g obtenNombre ( ) {
13 return nombre ;
14 }
15
16 public void mensaje ( ) {
17 System . out . p r i n t l n ( ”Mi nombre e s ” + obtenNombre ( ) ) ;
18 }
19 }

Ejemplo 2.5: Definición de la clase Parvulo3

El Ejemplo 2.5 hace uso de dicho principio al definir, con un acceso restrin-
gido o privado (private), el atributo nombre (lı́nea 6) para la clase Parvulo3.
Observe que dicha clase define también tres métodos públicos, los cuales es-
tablecen la interfaz de los objetos que sean instanciados:

1. estableceNombre: este método es utilizado comúnmente para ajus-


tar o establecer el valor de un atributo, y en principio, deberı́a ha-
ber un método de este tipo por cada atributo que contenga la clase y
que se requiera manipular desde el exterior. Este tipo de métodos son
comúnmente referidos como métodos de tipo set.

2. obtenNombre: este método es utilizado comúnmente para recuperar


u obtener el valor de un atributo, y al igual que antes, deberı́a ha-
ber un método de este tipo por cada atributo que contenga la clase y
20 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

que se requiera visualizar desde el exterior. Este tipo de métodos son


comúnmente referidos como métodos de tipo get.

3. mensaje: este tipo de métodos ha sido descrito con anterioridad. Note


que el método está definido como el del Ejemplo 2.1 (sin argumentos),
pero funciona como el del Ejemplo 2.3. Asegúrese de comprender ésto.

Observe que el método mensaje (lı́neas 16-18) se vale del método obten-
Nombre (lı́nea 17) para acceder al atributo nombre, pero no necesariamente
tiene que ser ası́, ya que un método puede acceder directamente a los atribu-
tos de la clase, siempre y cuando ambos estén definidos dentro de la misma
clase.
1 /∗ C l a s e de p r u e b a para P a r v u l o 3 . Se c r e a e l o b j e t o ” p a r v u l o ” i n s t a n c i a d o
2 de l a c l a s e P a r v u l o 3 y s e l e e n v i a n c u a t r o mens ajes .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s PruebaParvulo3 {
6 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
7 P a r v u l o 3 p a r v u l o = new P a r v u l o 3 ( ) ;
8
9 System . out . p r i n t l n ( ”Nombre d e l p a r v u l o : ” + p a r v u l o . obtenNombre ( ) ) ;
10 parvulo . estableceNombre ( ” Ricardo ” ) ;
11 System . out . p r i n t l n ( ”Nombre d e l p a r v u l o : ” + p a r v u l o . obtenNombre ( ) ) ;
12 p a r v u l o . mensaje ( ) ;
13 }
14 }

Ejemplo 2.6: Clase de prueba para la clase Parvulo3

Los métodos de tipo set sólo deben trabajar sobre un atributo, por lo
que habitualmente sólo reciben un argumento, mismo que corresponde con
la clase (tipo) del atributo a modificar2 .
De manera análoga, los métodos de tipo get no reciben ningún tipo de
argumentos, y la clase de objetos que regresan, está directamente relacionada
con la clase del atributo al que accederán 3 .
Es importante hacer notar también que la clase Parvulo4, a diferencia
de las anteriores, establece ya una caracterı́stica representada y definida por
el atributo nombre, de tal forma que los objetos derivados de ella (párvu-
los4 ), compartirán dicha caracterı́stica, aunque cada uno poseerá su propia
identidad (nombre).
2
String para el caso del Ejemplo 2.5.
3
Ídem.
4
Un párvulo es un niño pequeño en edad preescolar.
2.1. MENSAJES Y MÉTODOS 21

Figura 2.3: Salida del Ejemplo 2.6

El Ejemplo 2.6 muestra la clase de prueba para la clase Parvulo3. Al igual


que en los ejemplos anteriores para las clases de prueba, observe que en la
lı́nea 7 se define y crea el objeto parvulo.
Las lı́neas 9 y 11 hacen uso del método obtenNombre a través del envı́o
del mensaje correspondiente, mientras que la lı́nea 10 envı́a el mensaje esta-
bleceNombre con un argumento especı́fico. Finalmente, la lı́nea 12 muestra el
envı́o del mensaje mensaje, el cual deberı́a resultarle ya familiar al lector.
La salida del Ejemplo 2.6 se muestra en la Figura 2.3. Observe que ini-
cialmente el atributo nombre del objeto parvulo no está definido, de ahı́ que
se imprima null en la salida estándar, el cual es el valor por omisión en Java
para las referencias a objetos que no han sido instanciadas, es decir, cuando
los objetos todavı́a no existen, y por consiguiente, no han sido inicializados.

2.1.4. Métodos y constructores


Un constructor es un método especial que se invoca implı́citamente cuan-
do se crea el objeto por medio de la cláusula new.
La cláusula new genera la memoria necesaria para representar al objeto
correspondiente, y lo inicializa por medio de un constructor. En este sentido,
puede haber más de una forma de inicializar un objeto y, en consecuencia,
más de un constructor.
El identificador o nombre de los métodos constructores debe coincidir con
el identificador o nombre de la clase que los define, y como puede haber más
de un constructor cuyo identificador es el mismo, la forma de distinguirse
entre sı́, es a través del número y clase o tipo de los argumentos que definen,
constituyendo con ello una forma de polimorfismo comúnmente conocida co-
mo sobrecarga5 . En la creación del objeto, se invoca el constructor que
coincida con el número y tipo de argumentos proporcionados a la cláusula
new.
Las lı́neas 9-11 del Ejemplo 2.7 muestran la definición del constructor
5
La sobrecarga se trata con un poco más de detalle en la Sección 2.1.5.
22 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

Parvulo4, observe cómo el nombre del constructor coincide exactamente con


el nombre de la clase. Dicho constructor define un único argumento n, el cual
es un objeto de la clase String.
La inicialización que hace el constructor Parvulo4 consiste únicamente
de la asignación del objeto n al atributo representado por el objeto nombre.
1 /∗ Ejemplo de e n v i o de mensaje e i n v o c a c i o n de metodos ( V e r si o n 1 . 3 ) .
2 Se a g r e g a un c o n s t r u c t o r , e l c u a l d e f i n e e l e s t a d o i n i c i a l d e l
3 objeto instanciado .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 public c l a s s P a r v u l o 4 {
7 private S t r i n g nombre ;
8
9 Parvulo4 ( S t r i n g n ) {
10 nombre = n ;
11 }
12
13 public void e s t a b l e c e N o m b r e ( S t r i n g n ) {
14 nombre = n ;
15 }
16
17 public S t r i n g obtenNombre ( ) {
18 return nombre ;
19 }
20
21 public void mensaje ( ) {
22 System . out . p r i n t l n ( ”Mi nombre e s ” + obtenNombre ( ) ) ;
23 }
24 }

Ejemplo 2.7: Definición de la clase Parvulo4

Es importante mencionar que la labor de inicialización de un constructor


en particular puede ser un proceso mucho más elaborado que el descrito hasta
ahora, y estará en función directa de las responsabilidades de inicialización
con que se quiera dotar al constructor, e indudablemente también, de la
problemática en particular que se esté resolviendo por medio del objeto en
cuestión.
Los elementos restantes de la clase Parvulo4 han sido previamente abor-
dados en las secciones anteriores.
El Ejemplo 2.8 presenta la clase de prueba para el Ejemplo 2.7. Observe
que a diferencia de los ejemplos anteriores, en la lı́nea 8 se proporciona un
argumento al constructor Parvulo4, lo cual hace que desde la creación del
objeto parvulo, le sea definido un nombre.
Observe también cómo la secuencia de mensajes subsecuentes coincide con
las del Ejemplo 2.6, excepto que en la lı́nea 11 del Ejemplo 2.8, se envı́a el
2.1. MENSAJES Y MÉTODOS 23

Figura 2.4: Salida del Ejemplo 2.8

mensaje estableceNombre al objeto parvulo para ponerle el nombre completo


(con apellidos) al párvulo.
1 /∗ C l a s e de p r u e b a para P a r v u l o 4 . Se c r e a e l o b j e t o ” p a r v u l o ” i n s t a n c i a d o
2 de l a c l a s e Parvulo4 , s e usa e l c o n s t r u c t o r d e f i n i d o , y s e l e e n v i a n
3 c u a t r o mensajes .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 public c l a s s PruebaParvulo4 {
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 P a r v u l o 4 p a r v u l o = new P a r v u l o 4 ( ” R i c a r d o ” ) ;
9
10 System . out . p r i n t l n ( ”Nombre d e l p a r v u l o : ” + p a r v u l o . obtenNombre ( ) ) ;
11 p a r v u l o . e s t a b l e c e N o m b r e ( ” R i c a r d o Ruiz R o d r i g u e z ” ) ;
12 System . out . p r i n t l n ( ”Nombre d e l p a r v u l o : ” + p a r v u l o . obtenNombre ( ) ) ;
13 p a r v u l o . mensaje ( ) ;
14 }
15 }

Ejemplo 2.8: Clase de prueba para la clase Parvulo4


Asegúrese de comprender antes de continuar, que la Figura 2.4 muestra
la salida correspondiente a la ejecución del Ejemplo 2.8.

2.1.5. Sobrecarga
La sobrecarga (overload) es un tipo de polimorfismo, que se caracteriza
por la capacidad de poder definir más de un método o constructor con el
mismo nombre (identificador), siendo distinguidos entre sı́ por el número y
la clase (tipo) de los argumentos que se definen.
El Ejemplo 2.9 muestra la sobrecarga de constructores. Note que las lı́neas
8-10 definen el mismo constructor que el del Ejemplo 2.7 excepto por el
nombre, y que se ha añadido o sobrecargado un nuevo constructor (lı́neas 12-
14), el cual recibe tres argumentos que representan el nombre (n), el primer
apellido (a1 ), y el segundo apellido (a2 ) de un párvulo.
La sobrecarga de constructores se da porque ambos constructores tiene el
mismo identificador (Parvulo5 ), pero tienen distinto número de parámetros.
No puede existir sobrecarga para constructores o métodos con el mismo
identificador, y el mismo número o clase (tipo) de parámetros, tiene que
24 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

Figura 2.5: Salida del Ejemplo 2.10

haber algo que los distinga entre sı́, ya que en otro caso, habrı́a ambigüedad.
Los métodos restantes del Ejemplo 2.9 ya ha sido comentados con ante-
rioridad.
1 /∗ Ejemplo de e n v i o de mensaje e i n v o c a c i o n de metodos ( V e r si o n 1 . 4 ) .
2 Se muestra l a s o b r e c a r g a de c o n s t r u c t o r e s .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s P a r v u l o 5 {
6 private S t r i n g nombre ;
7
8 Parvulo5 ( S t r i n g n ) {
9 nombre = n ;
10 }
11
12 P a r v u l o 5 ( S t r i n g n , S t r i n g a1 , S t r i n g a2 ) {
13 nombre = n + ” ” + a1 + ” ” + a2 ;
14 }
15
16 public void e s t a b l e c e N o m b r e ( S t r i n g n ) {
17 nombre = n ;
18 }
19
20 public S t r i n g obtenNombre ( ) {
21 return nombre ;
22 }
23
24 public void mensaje ( ) {
25 System . out . p r i n t l n ( ”Mi nombre e s ” + obtenNombre ( ) ) ;
26 }
27 }

Ejemplo 2.9: Definición de la clase Parvulo5

La clase de prueba para el Ejemplo 2.9 se muestra en el Ejemplo 2.10,


y es también muy similar a las clases de prueba anteriormente explicadas.
Únicamente cabe resaltar la creación del objeto parvulo en la lı́nea 7, note
que ahora se le proporcionan tres argumentos al constructor, lo cual hace que
el constructor utilizado sea el definido en las lı́neas 12-14 del Ejemplo 2.9.
La salida del Ejemplo 2.10 aparece en la Figura 2.5. Compare dicha salida
con la de la Figura 2.4 y asegúrese de comprender la diferencia.
1 /∗ C l a s e de p r u e b a para P a r v u l o 5 . Se c r e a e l o b j e t o ” p a r v u l o ” i n s t a n c i a d o
2 de l a c l a s e P a r v u l o 5 mostrando l a s o b r e c a r g a de c o n s t r u c t o r e s .
2.2. HERENCIA 25

3 @autor Ricardo Ruiz R o d r i g u e z


4 ∗/
5 public c l a s s PruebaParvulo5 {
6 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
7 P a r v u l o 5 p a r v u l o = new P a r v u l o 5 ( ” R i c a r d o ” , ” Ruiz ” , ” R o d r i g u e z ” ) ;
8
9 System . out . p r i n t l n ( ”Nombre d e l p a r v u l o : ” + p a r v u l o . obtenNombre ( ) ) ;
10 parvulo . estableceNombre ( ” Ricardo ” ) ;
11 System . out . p r i n t l n ( ”Nombre d e l p a r v u l o : ” + p a r v u l o . obtenNombre ( ) ) ;
12 p a r v u l o . mensaje ( ) ;
13 }
14 }

Ejemplo 2.10: Clase de prueba para la clase Parvulo5

2.2. Herencia
Todos los conceptos del paradigma orientado a objetos discutidos en el
Capı́tulo 1 son importantes, pero el concepto de herencia es uno de los más
importantes, ya que dicho mecanismo de abstracción permite la reutilización
de código de una manera sumamente conveniente, y habilita las capacidades
del polimorfismo a través de la sobre escritura de métodos.

2.2.1. Abstracción
La descripción del concepto de herencia estará basado en los Ejemplos
2.11 y 2.12, pero para poder describirlos, considero pertinente presentar pri-
mero en un diagrama, los detalles de la relación que se quiere ejemplificar
para elevar el nivel de abstracción, es decir, a la forma en que las personas
comprendemos y analizamos las cosas, para posteriormente profundizar con
más conocimiento de causa, en los detalles de la implementación del concepto
en un lenguaje de programación.
El diagrama de clases UML6 del que partirá el análisis se muestra en la
Figura 2.6.
Los detalles completos de la explicación de un diagrama de clases UML
quedan fuera de los alcances de este libro, y sólo se describirán los aspectos
más relevantes que ayuden al lector a visualizar de mejor manera la herencia,
en caso de que el lector no cuente con experiencia en UML.
6
Leguaje de Modelado Unificado (Unified Modeling Language).
26 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

Figura 2.6: Diagrama de clases UML para la relación de herencia entre Cien-
tifico y Persona

Un diagrama de clases UML está compuesto, grosso modo, por clases y


las relaciones entre dichas clases. Por otro lado, cada clase es un recuadro
dividido en tres partes:

1. Identificador de la clase.

2. Listado de atributos con la especificación de su clase (tipo) y sus niveles


de acceso correspondientes.

3. Listado de métodos con la especificación de la clase (tipo) de sus ar-


gumentos y valor de retorno, ası́ como los niveles de acceso correspon-
dientes para los métodos.

Tanto para el caso de los atributos como para el de los métodos, los niveles
de acceso están representados por un signo de más para un acceso público
(+), y un signo de menos para un acceso privado (-).
En base a lo anterior, puede observarse de la Figura 2.6 que la clase
Persona, y por lo tanto las instancias que se deriven de ella, tendrán las
siguientes caracterı́sticas (atributos) comunes a una persona: un nombre, una
edad, y una nacionalidad7 . Observe también que se ha definido un conjunto
de operaciones, acciones, responsabilidades o comportamiento comunes a una
persona, definido por los métodos.
7
Podrı́an definirse mucho más caracterı́sticas, o caracterı́sticas diferentes a las descritas,
pero no es la intención del ejemplo representar las caracterı́sticas completas y comunes a
una persona, y lo mismo ocurre para el comportamiento o las responsabilidades represen-
tadas en los métodos.
2.2. HERENCIA 27

Caracterı́sticas más caracterı́sticas menos, acciones más, acciones menos,


es posible decir que una persona en promedio está en general representada
por la clase Persona.
Ahora bien, un cientı́fico es 8 una persona, y por lo tanto comparte las
caracterı́sticas y las acciones inherentes a una persona, y es precisamente
esta relación de compartir, la que se refiere a la herencia, ya que se dice que
en la herencia, una clase hereda (comparte) los atributos (caracterı́sticas) y
métodos (acciones) a otra.
La herencia en UML se representa por medio de una flecha como la de la
Figura 2.6, y es importante señalar que el sentido de la flecha es muy impor-
tante, ya que tal y como aparece en la figura, indica que la clase Cientifico
hereda las caracterı́sticas y el comportamiento de la clase Persona (y no al
revés).
Note que la clase Cientifico define un atributo adicional (especialidad),
mismo que se añade a todos los atributos que implı́citamente ya tiene, los
cuales fueron heredados de la clase Persona. Observe también que se han de-
finido cuatro métodos para la clase Cientifico, los cuales tienen las siguientes
caracterı́sticas:
estableceEspecialidad : es un método set para el atributo especialidad
definido en la clase Cientifico.
obtenEspecialidad : es un método get para el atributo especialidad defi-
nido en la clase Cientifico.
mensaje : este método ya estaba definido en la clase Persona, pero al ser
redefinido en la clase Cientifico, se dice que sobre escribe (override) al
primero, lo cual significa que un objeto instanciado de la clase Cien-
tifico, responderá al mensaje mensaje con la definición de su propio
método, y no con la definición del método mensaje definido en la clase
Persona.
mensajeEspecial : este es un nuevo método particular y especı́fico a las
instancias derivadas de la clase Cientifico.
Es sumamente importante que el lector se asegure de comprender la
descripción hasta aquı́ realizada respecto a las clases Persona y Cientifi-
co, ası́ como de entender la relación existente entre ellas antes de continuar
8
Recuerde la idea de división en especializaciones (relación is-a) presentada en el
Capı́tulo 1.
28 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

a la siguiente sección, en donde se abordarán los aspectos relacionados con


la implementación.

2.2.2. Implementación
El Ejemplo 2.11 muestra la implementación en Java de la clase Persona
mostrada en la Figura 2.6. Todos los detalles de la clase Persona del Ejemplo
2.11 ya han sido discutidos con anterioridad, por lo que es importante que
el lector los revise, analice, y compare, con los elementos del diagrama UML
descritos en la sección anterior, y que se asegure de comprender la relación
que existe entre ellos.
1 /∗ Ejemplo de h e r e n c i a .
2 La c l a s e Persona s e r a l a c l a s e pad re ( s u p e r c l a s e )
3 de l a c l a s e C i e n t i f i c o .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 public c l a s s Persona {
7 // A t r i b u t o s de l a c l a s e
8 private S t r i n g nombre ;
9 private i n t edad ;
10 private S t r i n g n a c i o n a l i d a d ;
11
12 // C o n s t r u c t o r de un argumento ( nombre )
13 Persona ( S t r i n g n ) {
14 nombre = n ;
15 }
16
17 // C o n s t r u c t o r de dos argumentos ( nombre y edad )
18 Persona ( S t r i n g n , i n t e ) {
19 nombre = n ;
20 edad = e ;
21 }
22
23 // C o n s t r u c t o r de t r e s argumentos ( nombre , edad y n a c i o n a l i d a d )
24 Persona ( S t r i n g n , i n t e , S t r i n g nac ) {
25 nombre = n ;
26 edad = e ;
27 n a c i o n a l i d a d = nac ;
28 }
29
30 // Metodo para e s t a b l e c e r ( s e t ) e l a t r i b u t o ”nombre”
31 public void e s t a b l e c e N o m b r e ( S t r i n g n ) {
32 nombre = n ;
33 }
34
35 // Metodo para o b t e n e r ( g e t ) e l a t r i b u t o ”nombre”
36 public S t r i n g obtenNombre ( ) {
37 return nombre ;
38 }
39
40 // Metodo para e s t a b l e c e r ( s e t ) e l a t r i b u t o ” edad ”
2.2. HERENCIA 29

41 public void e s t a b l e c e E d a d ( i n t e ) {
42 edad = e ;
43 }
44
45 // Metodo para o b t e n e r ( g e t ) e l a t r i b u t o ” edad ”
46 public i n t obtenEdad ( ) {
47 return edad ;
48 }
49
50 // Metodo para e s t a b l e c e r ( s e t ) e l a t r i b u t o ” n a c i o n a l i d a d ”
51 public void e s t a b l e c e N a c i o n a l i d a d ( S t r i n g n ) {
52 nacionalidad = n ;
53 }
54
55 // Metodo para o b t e n e r ( g e t ) e l a t r i b u t o ” n a c i o n a l i d a d ”
56 public S t r i n g o b t e n N a c i o n a l i d a d ( ) {
57 return n a c i o n a l i d a d ;
58 }
59
60 // Metodo para im p r i m ir un mensaje en l a s a l i d a e s t a n d a r
61 public void mensaje ( ) {
62 System . out . p r i n t l n ( ” Puedo h a b l a r , mi nombre e s ” + obtenNombre ( ) ) ;
63 }
64
65 // Metodo que s i m u l a l a a c c i o n de comer por p a r t e de una p e r s o n a
66 public void comer ( ) {
67 System . out . p r i n t l n ( ”Mmmmmm uno de l o s p l a c e r e s de l a v i d a . . . ” ) ;
68 }
69 }

Ejemplo 2.11: Definición de la clase Persona

Por otro lado, el Ejemplo 2.12 contiene la implementación de la clase


Cientifico mostrada en la Figura 2.6. Observe que la herencia es implemen-
tada en Java a través del uso de la cláusula extends (lı́nea 6), seguida de la
clase de la que se hereda (Persona) en la definición de la clase Cientifico.
Es importante que el lector note el uso de la cláusula super en los cons-
tructores (lı́neas 11 y 18). El uso en Java de dicha cláusula sólo puede hacerse
en el contexto de constructores, y sirve para delegarle al constructor corres-
pondiente de la clase padre, los detalles de inicialización del objeto en cuestión
que, para el caso de la clase Cientifico, corresponden a los constructores de
dos y tres argumentos de la clase Persona. Asegúrese de comprender ésto
antes de continuar.
Al igual que antes, se deja como ejercicio de análisis para el lector, tanto
los detalles restantes de implementación de la clase Cientifico, como el asegu-
rarse de comprender la relación del diagrama UML con el código del Ejemplo
2.12.
30 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

En este punto, es importante también que el lector ponga especial aten-


ción en los métodos mensaje del Ejemplo 2.11 (lı́neas 61-63), y del Ejemplo
2.12 (lı́neas 33-35), ya que serán fundamentales para comprender la sobre es-
critura de métodos que se discute más adelante en la clase de prueba (Ejemplo
2.13).
1 /∗ Ejemplo de h e r e n c i a .
2 La c l a s e C i e n t i f i c o h e r e d a l a s c a r a c t e r i s t i c a s ( a t r i b u t o s )
3 y o p e r a c i o n e s ( metodos ) de l a c l a s e Persona .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 public c l a s s C i e n t i f i c o extends Persona {
7 private S t r i n g e s p e c i a l i d a d ;
8
9 // C o n s t r u c t o r de t r e s argumentos ( nombre , edad y e s p e c i a l i d a d )
10 C i e n t i f i c o ( S t r i n g n , int e , S t r i n g esp ) {
11 super ( n , e ) ;
12 e s p e c i a l i d a d = esp ;
13 }
14
15 // C o n s t r u c t o r de c u a t r o argumentos ( nombre , edad ,
16 // n a c i o n a l i d a d y e s p e c i a l i d a d )
17 C i e n t i f i c o ( S t r i n g n , i n t e , S t r i n g nac , S t r i n g e s p ) {
18 super ( n , e , nac ) ;
19 e s p e c i a l i d a d = esp ;
20 }
21
22 // Metodo para e s t a b l e c e r ( s e t ) e l a t r i b u t o ” e s p e c i a l i d a d ”
23 public void e s t a b l e c e E s p e c i a l i d a d ( S t r i n g e ) {
24 especialidad = e ;
25 }
26
27 // Metodo para o b t e n e r ( g e t ) e l a t r i b u t o ” e s p e c i a l i d a d ”
28 public S t r i n g o b t e n E s p e c i a l i d a d ( ) {
29 return e s p e c i a l i d a d ;
30 }
31
32 // Metodo para im p r i m ir un mensaje en l a s a l i d a e s t a n d a r
33 public void mensaje ( ) {
34 System . out . p r i n t l n ( ”Yo , ” + obtenNombre ( ) + ” , s o y ” +
obtenEspecialidad () ) ;
35 }
36
37 // Metodo para im p r i m ir un mensaje e s p e c i a l en l a s a l i d a e s t a n d a r
38 public void m e n s a j e E s p e c i a l ( ) {
39 System . out . p r i n t l n ( ”La d i s t a n c i a d e l s o l a l a t i e r r a e s 149 600 000
kms” ) ;
40 }
41 }

Ejemplo 2.12: Definición de la clase Cientifico

La clase de prueba para la herencia se presenta en el Ejemplo 2.13 y


se describe a continuación. La lı́nea 10 define el objeto persona y genera
2.2. HERENCIA 31

Figura 2.7: Salida del Ejemplo 2.13

una instancia de la clase Persona con caracterı́sticas especı́ficas; note que


el constructor que está siendo utilizado es el definido en las lı́neas 24-28 del
Ejemplo 2.11.
Observe cómo en las lı́neas 13, 15, 19 y 21 del Ejemplo 2.13, se envı́an
mensajes especı́ficos al objeto persona para obtener su nombre, cambiarle el
nombre, volver a obtener su nombre, solicitarle comer y un mensaje respec-
tivamente; cuya salida se ve expresada en las primeras cuatro lı́neas de la
Figura 2.7.
Por otro lado, la lı́nea 26 define y crea el objeto cientifico como una
instancia de la clase Cientifico. Al igual que antes, note cómo el constructor
utilizado es el definido en las lı́neas 17-20 del Ejemplo 2.12.
A su vez, las lı́neas 28 y 30 del Ejemplo 2.13, realizan algo semejante
a lo que se hizo con el objeto persona, es decir, envı́an mensajes al objeto
cientifico para obtener y cambiar el nombre del cientı́fico respectivamente.
La lı́nea 32 por su parte, cambia la especialidad del cientı́fico a través de
un mensaje de tipo set.
Finalmente, las lı́neas 34, 36, 38 y 40, envı́an mensajes especı́ficos al
objeto cientifico para obtener su nombre y solicitarle comer, un mensaje
y un mensaje especial respectivamente. Note cómo para el mensaje mensaje,
las respuestas del objeto persona y el objeto cientifico difieren por completo,
debido a que aunque el mensaje es el mismo, el objeto que los recibe responde
de manera distinta a dicho mensaje; ésto último fue lo que en el Capı́tulo 1
se definió como polimorfismo9 .
La salida del Ejemplo 2.13 se muestra en la Figura 2.7.
9
El polimorfismo está expresado en el ejemplo en su forma de sobre escritura (override)
de métodos.
32 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

1 /∗ C l a s e de p r u e b a para l a h e r e n c i a .
2 s e l e e n v i a n mensajes . P o s t e r i o r m e n t e s e c r e a e l o b j e t o ” p e r s o n a ”
3 i n s t a n c i a d o de l a c l a s e C i e n t i f i c o y tambien s e l e e n v i a n mensajes ,
4 n o t e que a l g u n o s mensajes son h e r e d a d o s de l a s u p e r c l a s e .
5 @autor Ricardo Ruiz R o d r i g u e z
6 ∗/
7 public c l a s s P r u e b a H e r e n c i a {
8 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
9 // Se c r e a e l o b j e t o ” p e r s o n a ” i n s t a n c i a d o de l a c l a s e Persona
10 Persona p e r s o n a = new Persona ( ” R i c a r d o ” , 3 8 , ” Mexicano ” ) ;
11
12 // Se imprime e l nombre d e l o b j e t o ” p e r s o n a ” a t r a v e s de un mensaje
13 System . out . p r i n t l n ( ”Nombre : ” + p e r s o n a . obtenNombre ( ) ) ;
14 // Se e s t a b l e c e un nuevo nombre para e l o b j e t o ” p e r s o n a ”
15 p e r s o n a . e s t a b l e c e N o m b r e ( ” R i c a r d o Ruiz R o d r i g u e z ” ) ;
16 // Se s o l i c i t a nuevamente a l o b j e t o ” p e r s o n a ” i m p r i m i r su nombre
17 System . out . p r i n t l n ( ”Nombre : ” + p e r s o n a . obtenNombre ( ) ) ;
18 // Se l e e n v i a a l o b j e t o ” p e r s o n a ” e l mensaje ”comer”
19 p e r s o n a . comer ( ) ;
20 // Se l e e n v i a a l o b j e t o ” p e r s o n a ” e l mensaje ” mensaje ”
21 p e r s o n a . mensaje ( ) ;
22
23 System . out . p r i n t l n ( ) ;
24
25 // Se c r e a e l o b j e t o ” c i e n t i f i c o ” i n s t a n c i a d o de l a c l a s e C i e n t i f i c o
26 C i e n t i f i c o c i e n t i f i c o = new C i e n t i f i c o ( ” C a r l Sagan ” , 6 2 ,
” E s t a d o u n i d e n s e ” , ” Astronomo ” ) ;
27 // Se imprime e l nombre d e l o b j e t o ” c i e n t i f i c o ” a t r a v e s de un mensaje
28 System . out . p r i n t l n ( ”Nombre : ” + c i e n t i f i c o . obtenNombre ( ) ) ;
29 // Se e s t a b l e c e un nuevo nombre para e l o b j e t o ” c i e n t i f i c o ”
30 c i e n t i f i c o . e s t a b l e c e N o m b r e ( ” C a r l Edward Sagan ” ) ;
31 // Se e s t a b l e c e una nueva e s p e c i a l i d a d para e l o b j e t o ” c i e n t i f i c o ”
32 c i e n t i f i c o . e s t a b l e c e E s p e c i a l i d a d ( ” Astronomo y A s t r o f i s i c o ” ) ;
33 // Se s o l i c i t a nuevamente a l o b j e t o ” c i e n t i f i c o ” i m p r i m i r su nombre
34 System . out . p r i n t l n ( ”Nombre : ” + c i e n t i f i c o . obtenNombre ( ) ) ;
35 // Se l e e n v i a a l o b j e t o ” c i e n t i f i c o ” e l mensaje ”comer”
36 c i e n t i f i c o . comer ( ) ;
37 // Se l e e n v i a a l o b j e t o ” c i e n t i f i c o ” e l mensaje ” mensaje ”
38 c i e n t i f i c o . mensaje ( ) ;
39 // Se l e e n v i a a l o b j e t o ” c i e n t i f i c o ” e l mensaje ” m e n s a j e E s p e c i a l ”
40 c i e n t i f i c o . mensajeEspecial () ;
41 // p e r s o n a . m e n s a j e E s p e c i a l ( ) ;
42 }
43 }

Ejemplo 2.13: Clase de prueba para la herencia


2.3. CONSIDERACIONES FINALES 33

2.3. Consideraciones finales


2.3.1. Respecto al envı́o de mensajes
La sintaxis general en Java para el envı́o de mensajes a un objeto es la
siguiente:

objeto.mensaje(lista de argumentos)

donde objeto es un objeto de una clase previamente definida, y mensaje es uno


de los métodos públicos definidos para dicha clase. La lista de argumentos es
una lista de argumentos separada por comas, en donde cada argumento puede
ser un objeto, o un tipo de dato primitivo.

2.3.2. Respecto a la sobrecarga de operadores


Algunos lenguajes de programación soportan un concepto relacionado con
sobrecarga de operadores. La idea general del concepto de sobrecarga se ha
planteado en la Sección 2.1.5. El lenguaje de programación Java no soporta
la sobrecarga de operadores, y por consiguiente, se ha omitido su descripción;
pero es importante que el lector conozca que el concepto de sobrecarga no es
exclusivo de los métodos o constructores, y mucho menos de un lenguaje de
programación en particular.

2.3.3. Respecto al paradigma


El establecimiento de niveles de acceso como private para los atributos
de una clase, y el uso de métodos de tipo set y get están directamente rela-
cionados con el principio de ocultación de información.
Ahora bien, es probable que el lector haya notado que en la descripción del
Ejemplo 2.13, en distintas ocasiones se hizo referencia a los objetos persona y
cientifico como si fueran en sı́ mismos personas o entidades existentes. Ésto
se hizo de manera deliberada, ya que como se comentó en el Capı́tulo 1,
el paradigma orientado a objetos eleva el nivel de abstracción, y hace que
se haga referencia a las entidades fundamentales del paradigma (objetos),
como elementos comunes de nuestro lenguaje natural, lo cual permite que los
problemas se puedan expresar de una manera más natural e intuitiva.
En éste sentido, al dotar a los objetos de una personalidad propia con
caracterı́sticas y responsabilidades, en lugar de pensar en términos de datos,
34 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

variables, y funciones o procedimientos que operen sobre dichos datos, se


eleva el nivel de abstracción, facilitando ası́ el análisis y la comprensión, ya
que el problema y su solución pueden ser expresados y analizados en términos
del dominio del problema, y no en el del medio (lenguaje de programación) de
la solución. Ésta es la forma en la que las personas abstraemos y utilizamos
la información.
Es sumamente importante que el lector tenga presente, que en el paradig-
ma orientado a objetos, no se piensa en términos de datos, sino en términos
de entidades con caracterı́sticas y responsabilidades especı́ficas, por lo que,
cuando defina una clase, puede resultarle útil el plantearse al menos un par
de preguntas10 que le permitan determinar si los los objetos derivados de su
clase tienen o no sentido, además de una alta cohesión:

¿Los atributos representan caracterı́sticas o propiedades, o definen un


estado para los objetos que serán instanciados?

¿La clase representa en sus métodos servicios, comportamiento, accio-


nes o responsabilidades inherentes a los objetos que deriven de ella?

Con estas dos sencillas preguntas, además de validar y verificar su diseño


de clases, estará reforzando el concepto de encapsulamiento.

10
Las preguntas propuestas son sólo una guı́a y una sugerencia al lector, no pretenden
ser de ninguna manera una lista completa y absoluta.
2.4. EJERCICIOS 35

2.4. Ejercicios
1. En el Ejemplo 2.5 se hizo referencia a la invocación del mensaje ob-
tenNombre (lı́nea 17) dentro del método mensaje. Cambie el método
obtenNombre por el atributo nombre y compruebe lo descrito en el
texto.
2. Considere el Ejemplo 2.6. ¿Qué sucede si en lugar de acceder al atributo
nombre por medio del método obtenNombre (lı́neas 9 y 11) se intenta
acceder directamente al atributo a través del operador punto?.
Para probar lo anterior, cambie la expresión:

parvulo.obtenNombre()

por la expresión:

parvulo.nombre

recompile y analice lo que suceda.


3. Modifique el Ejemplo 2.6 para que genere más de un objeto de la clase
Parvulo3 del Ejemplo 2.5, de tal forma que tenga un conjunto de al
menos tres párvulos.
Para los objetos creados, definales una identidad a cada uno de los
párvulos instanciados por medio de la asignación de un nombre distinto
a cada uno de ellos, envı́eles mensajes, experimente y juegue con ellos,
recuerde que sus objetos son, al fin y al cabo, niñ@s pequeñ@s.
4. Para el Ejemplo 2.7, defina y añada un constructor sin argumentos, de
tal forma que la labor del constructor sea definir su propio nombre (el
nombre del lector) al atributo nombre.
Modifique en consecuencia la clase del Ejemplo 2.8 para que haga uso
del constructor recién definido, y compruebe su funcionamiento.
5. La Sección 2.1.5 abordó el tema de la sobrecarga, y ejemplificó el con-
cepto utilizando sobrecarga de constructores. La sobrecarga de métodos
es análoga a la de constructores.
36 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

Modifique el Ejemplo 2.9 para que implemente la sobrecarga de méto-


dos de la siguiente manera:

a) Defina un nuevo método con la siguiente firma:


public void mensaje(String m)

b) La responsabilidad del nuevo método mensaje, será la de imprimir


en la salida estándar la leyenda “Mensaje recibido”, seguido de la
cadena m.
c) Realice una clase de prueba (puede basarse en la del Ejemplo
2.10) para comprobar el funcionamiento de todos los métodos,
especialmente, el método sobrecargado mensaje.

6. Considere el Ejemplo 2.9 y modifı́quelo para que, en lugar de uno,


defina tres atributos:

nombre (String).
apellido1 (String).
apellido2 (String).

En base a lo anterior:

a) Agregue el método set correspondiente para cada uno de los atri-


butos, en base a lo descrito en el texto.
b) Agregue el método get correspondiente para cada uno de los atri-
butos, también en base a lo descrito en el texto.
c) Agregue un constructor para que sea posible construir un objeto
(párvulo) utilizando únicamente el nombre, y el primer apellido.
d ) Modifique el método mensaje para que, en caso de que alguno de
los atributos del objeto en cuestión sea null, dicho atributo sea
ignorado y en consecuencia, no se imprima en la salida estándar.
e) Construya una clase de prueba que demuestre tanto el funciona-
miento de todas las posibles opciones de construcción de objetos,
como el adecuado funcionamiento de los métodos set, get y men-
saje.
2.4. EJERCICIOS 37

7. El Ejemplo 2.13 tiene la lı́nea 41 comentada, por lo que el compilador


y la JVM la ignoran. Si la descomenta ¿qué piensa que sucederá?,
¿compilará?, ¿se ejecutará?, ¿imprimirá algo en la salida estándar?,
¿fallará la ejecución?.
Después de analizar el programa, responda dichas preguntas, y corro-
bore su respuesta con la experimentación.
8. Modifique el Ejemplo 2.11 para que contenga más atributos, es decir,
para que las caracterı́sticas de una persona estén más completas. No
olvide agregar métodos de tipo set y get por cada uno de los atributos
que añada.
Para los atributos, añada al menos los siguientes cambios:
Cambie el atributo nombre por una distribución más convencional:
nombre, primer apellido, y segundo apellido.
Agregue un atributo que represente la dirección o domicilio.
Agregue un atributo que represente el sexo, femenino o masculino,
según sea el caso.
Después de lo anterior:
a) Compruebe el adecuado funcionamiento de la clase Persona res-
pecto de las modificaciones recién hechas. Asegúrese de comprobar
que los métodos set y get trabajan de la manera esperada.
b) Compruebe que con los cambios realizados a la clase Persona, los
aspectos relacionados con la herencia de la clase Cientifico del
Ejemplo 2.12, siguen funcionando sin ningún tipo de problema.
c) Modifique la clase Cientifico de manera análoga, y compruebe
también que el mecanismo de la herencia permanece inalterado.
Para la clase Cientifico agregue dos atributos (y sus correspon-
dientes métodos de acceso):
1) Institución o lugar donde labora.
2) Grado de estudios.
d ) Agregue nuevas acciones, comportamientos o responsabilidades a
las clases Persona y Cientifico, y asegúrese de probar su adecua-
do funcionamiento. No olvide experimentar con los conceptos de
sobrecarga y sobre escritura.
38 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS

9. Construya un ejemplo completamente diferente al visto en el texto,


donde ponga de relieve los conceptos de envı́o de mensajes, herencia,
polimorfismo, encapsulamiento y ocultación de información.
Para realizar lo anterior, puede apoyarse de la jerarquı́a de clases de la
Figura 1.2, o de alguna otra figura o idea que sea de su preferencia.
Capı́tulo 3

Estructuras de datos

Ask not what you can do to your data structures, but rather ask
what your data structures can do for you.

3.1. Panorama general


De manera general, es posible decir que una computadora es una máquina
que manipula y procesa datos.
Las estructuras de datos están relacionadas con el estudio de cómo es que
se organizan los datos dentro de una computadora, y de cómo se manipulan,
procesan y emplean dichos datos, para representar información que sea de
utilidad para las personas.
Existen muchos tipos de estructuras de datos, y para cada estructura de
datos, hay diferentes variaciones que las particularizan para un contexto de
uso especı́fico.
Un tratado amplio y completo de las estructuras de datos, queda fuera
de los alcances de este libro, por lo que sólo se mencionarán algunas de las
estructuras de datos más comunes y convencionales, ası́ como algunas de sus
variaciones.
A continuación se presenta una descripción muy general de las estructuras
de datos que se analizarán en los capı́tulos siguientes:

Pilas : son estructuras de datos ampliamente utilizadas y sumamente im-


portantes en compiladores y sistemas operativos por ejemplo. En una
pila, las inserciones y las eliminaciones se efectúan únicamente en un
extremo de la estructura de datos: su parte superior.

39
40 CAPÍTULO 3. ESTRUCTURAS DE DATOS

Colas de espera : este tipo de estructuras de datos representan en general


lı́neas de espera; las inserciones se efectúan en la parte posterior de la
misma, y las eliminaciones se hacen por la parte delantera.
Listas enlazadas : son colecciones de elementos de datos alineados en una
fila. En una lista enlazada, las inserciones y las eliminaciones se efectúan
en cualquier parte de la estructura de datos.
Árboles binarios : son estructuras de datos que facilitan la búsqueda, la
clasificación u ordenamiento de los datos a alta velocidad, la elimina-
ción de elementos duplicados, la representación de sistemas de archivos
y directorios, y las expresiones de compilación entre otras muchas apli-
caciones.
Cada una de estas estructuras de datos tienen muchas otras, y muy in-
teresantes aplicaciones, algunas de las cuales, se presentan y discuten en los
capı́tulos correspondientes a cada una de ellas.

3.2. Tipos de datos y referencias


Probablemente el lector esté familiarizado con el concepto de tipo de
dato, ya sea debido a que tenga experiencia previa con algún lenguaje de
programación, o simplemente porque ha revisado el material del Apéndice A
o del Capı́tulo 2. Entonces ¿podrı́a definir qué es un tipo de dato?.
Un tipo de dato es un método para interpretar un patrón de bits. En
éste sentido, una secuencia de bits determinada podrı́a ser interpretada de
una forma u otra, en función del tipo de dato.
En la mayorı́a de los lenguajes de programación, el programador especifi-
ca, mediante las declaraciones de variables u objetos, cómo es que el programa
va a interpretar el contenido de la memoria de la computadora.
Las declaraciones también le especifican al compilador exactamente, qué es
lo que representan los sı́mbolos de las operaciones que se utilizarán, ya que
aunque en apariencia es lo mismo, los mecanismos de implementación de una
operación aritmética tan aparentemente simple como la adición, difieren de
un tipo de dato a otro.
Por otro lado, para la mayorı́a de los lenguajes de programación, los
nombres de variables o identificadores asociados a los objetos, son en reali-
dad referencias, es decir, direcciones de memoria en las que se almacenan
fı́sicamente los objetos a los que hacen referencia.
3.3. TIPOS DE DATOS ABSTRACTOS (ADT) 41

El manejo de referencias o direcciones de memoria puede ser explı́cito,


como en el caso de los lenguajes C y C++ por ejemplo, donde el manejo
de referencias se realiza a través de apuntadores. En algunos otros lenguajes
como Java y C#, el manejo de referencias es implı́cito, es decir, se realiza a
través del nombre o identificador de los objetos.
Tanto el manejo de referencias explı́cito como el implı́cito, tienen sus
ventajas y desventajas, y las fortalezas de uno, son las debilidades del otro
y viceversa. No es la intención de esta sección ni la del libro extenderse en
dichos aspectos, ya que para los objetivos especı́ficos que se persiguen en
este texto, basta con saber que Java hace uso de referencias implı́citas, y que
el nombre los objetos son en realidad dichas referencias y no los objetos en
sı́ mismos. En la Sección 3.4.1 se detalla y ejemplifica el manejo de referencias
para los objetos.

3.3. Tipos de datos abstractos (ADT)


Desde el punto de vista de la programación, es importante que los pro-
gramadores puedan definir sus propias abstracciones de datos, de tal forma
que dichas abstracciones trabajen de manera parecida a las abstracciones o
a las primitivas de datos proporcionadas por el sistema subyacente.
Un tipo de dato abstracto o ADT (Abstract Data Type), es definido
por una especificación abstracta, es decir, permite especificar las propiedades
lógicas y funcionales de un tipo de dato.
Desde el punto de vista de los ADT, un tipo de dato es un conjunto de
valores y un grupo de operaciones definidas sobre dichos valores. El conjunto
de valores y el de las operaciones, forman una estructura matemática que
se implementa usando una estructura de datos particular de hardware o de
software.
El concepto de ADT está relacionado con la concepción matemática que
define al tipo de datos, por lo que, al definir un ADT como concepto ma-
temático, no interesa la eficiencia del espacio o del tiempo1 , sino las pro-
piedades y caracterı́sticas inherentes a él. La definición de un ADT no se
relaciona en lo absoluto con los detalles de la implementación; de hecho, tal
vez ni siquiera sea posible implementar un ADT particular en ningún tipo
de hardware o software 2 .
1
Los cuales son aspectos relacionados con la implementación del ADT.
2
Piense por ejemplo en los números reales y en la propiedad de la densidad de los
42 CAPÍTULO 3. ESTRUCTURAS DE DATOS

Un ADT consta de dos partes: una definición de valor y una definición


de operador, mismas que se describen a continuación:

1. La definición del valor establece el conjunto de valores para el ADT,


y consta a su vez de dos partes:

a) Una cláusula de definición.


b) Una cláusula de condición3 .

2. En la definición del operador, cada operador está definido como una


operación abstracta, con condiciones previas opcionales, y las condicio-
nes posteriores o postcondiciones.

Por otro lado, para la implementación de un ADT, es necesario tomar en


cuenta los siguientes aspectos:

1. Hacer disponible (pública) la definición del nuevo tipo.

2. Hacer disponibles un conjunto de operaciones que puedan ser utilizadas


para manipular las instancias del tipo definido (métodos públicos de
servicios).

3. Proteger los datos asociados con el tipo de dato que está siendo definido
(atributos privados), de tal forma que dichos datos sólo puedan ser
accedidos por medio de los métodos proporcionados para ello.

4. Poder generar múltiples instancias del tipo definido, preferentemente,


con más de una opción de inicialización, siempre que ésto no entre en
contradicción con la definición o restricciones del tipo.

Es importante resaltar que, asociado con un ADT particular, puede haber


una o más implementaciones distintas.
En resumen, los ADT son un mecanismo de abstracción sumamente im-
portante y, dado que la programación orientada a objetos (POO) se funda-
menta en la abstracción, es posible decir que constituye uno de los pilares de
la orientación a objetos.
números reales.
3
Para la definición de un ADT racional por ejemplo, una cláusula de condición estarı́a
relacionada con la restricción de que el denominador de un número racional, no puede ser
cero.
3.3. TIPOS DE DATOS ABSTRACTOS (ADT) 43

3.3.1. Especificación del ADT Racional


Existen varios métodos para especificar un ADT, desde notaciones ma-
temáticas, hasta descripciones detalladas hechas en lenguaje natural. Lo im-
portante de la especificación es que sea clara y no ambigüa.
Esta sección hará uso de la notación matemática para la especificación
del ADT racional.
Sea r un número racional. Se define r como el cociente de dos números
enteros, es decir, r = pq donde p, q ∈ Z, ∀q = 0.
Las operaciones aritméticas de suma, resta, multiplicación y división de
dos números racionales, se especifica a continuación:
Sean r1 y r2 dos números racionales, es decir: r1 , r2 ∈ Q definidos como:
p1
r1 = ; p1 , q1 ∈ Z, q1 = 0 (3.1)
q1
p2
r2 =; p2 , q2 ∈ Z, q2 = 0 (3.2)
q2
la suma de r1 y r2 se define de la siguiente manera:

p 1 p2 (p1 ∗ q2 ) + (q1 ∗ p2 )
r 1 + r2 =
+ = (3.3)
q1 q2 q1 ∗ q 2
y la resta de manera análoga:

p 1 p2 (p1 ∗ q2 ) − (q1 ∗ p2 )
r 1 − r2 =− = (3.4)
q1 q2 q1 ∗ q 2
mientras que la multiplicación está dada por:
p1 p2 p1 ∗ p2
r1 ∗ r 2 = ∗ = (3.5)
q1 q2 q1 ∗ q 2
y la división por:
p1
r1 q1 p 1 ∗ q2
= p2 = (3.6)
r2 q2
q1 ∗ p 2
Observe que la especificación de los valores para el ADT racional está dada
por las Ecuaciones 3.1 y 3.2, y que existe una restricción sobre el valor del
denominador representado por q1 y q2 respectivamente.
Finalmente, note que la especificación de las operaciones aritméticas para
el ADT está dada por las Ecuaciones 3.3, 3.4, 3.5 y 3.6.
44 CAPÍTULO 3. ESTRUCTURAS DE DATOS

Figura 3.1: Salida de una ejecución del Ejemplo 3.2 al intentar crear un
número racional cuyo denominador sea cero

3.3.2. Implementación del ADT Racional


El Ejemplo 3.1 muestra una posible implementación de ADT racional
definido en la sección anterior.
Las lı́neas 7 y 8 definen los atributos de un número racional ( pq ). Los
detalles relacionados con los métodos de tipo set y get (lı́neas 25-41), ya han
sido comentados en el Capı́tulo 2 y no se repetirán aquı́.
Por otro lado, los constructores definidos (lı́neas 10-23) requieren de una
ampliación en su explicación, ya que difieren un poco de lo hasta ahora
comentado para los constructores.
El constructor principal o base, es el definido en las lı́neas 18-23. Ob-
serve que dicho constructor recibe dos argumentos, mismos que representan
el numerador (n) y el denominador (d ) del número racional que se desea
inicializar. En la lı́nea 19, el constructor realiza una verificación del denomi-
nador (cláusula de condición), de tal forma que si éste es cero, se crea (new)
y lanza (throw) una excepción4 para indicar el error a través de la clase
RuntimeException5 (vea la Figura 3.1).
1 /∗ Ejemplo de ADT.
2 La c l a s e R a c i o n a l e s una a b s t r a c c i o n s e n c i l l a de l o s numeros r a c i o n a l e s
3 cuya forma e s p / q , donde p y q son numeros e n t e r o s .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 public c l a s s R a c i o n a l {
7 private i n t p ;
8 private i n t q ;
9
10 Racional () {
11 this (1 , 1) ;
12 }
13
14 Racional ( int n ) {
15 this (n , 1) ;
16 }

4
Vea la Sección A.5.5 del Apéndice A.
5
Note también que el mismo comportamiento es considerado en las lı́neas 30 y 31 para
el método estableceDenominador.
3.3. TIPOS DE DATOS ABSTRACTOS (ADT) 45

17
18 Racional ( int n , int d ) {
19 i f ( d == 0 )
20 throw new RuntimeException ( ” El denominador no puede s e r c e r o . ” ) ;
21 p = n;
22 q = d;
23 }
24
25 public void e s t a b l e c e N u m e r a d o r ( i n t n ) {
26 p = n;
27 }
28
29 public void e s t a b l e c e D e n o m i n a d o r ( i n t d ) {
30 i f ( d == 0 )
31 throw new RuntimeException ( ” El denominador no puede s e r c e r o . ” ) ;
32 q = d;
33 }
34
35 public i n t obtenNumerador ( ) {
36 return p ;
37 }
38
39 public i n t obtenDenominador ( ) {
40 return q ;
41 }
42
43 public R a c i o n a l suma ( R a c i o n a l r ) {
44 R a c i o n a l s = new R a c i o n a l ( ) ;
45
46 s . p = p ∗ r . obtenDenominador ( ) + q ∗ r . obtenNumerador ( ) ;
47 s . q = q ∗ r . obtenDenominador ( ) ;
48
49 return s ;
50 }
51
52 public R a c i o n a l m u l t i p l i c a ( R a c i o n a l r ) {
53 R a c i o n a l m = new R a c i o n a l ( ) ;
54
55 m. p = p ∗ r . obtenNumerador ( ) ;
56 m. q = q ∗ r . obtenDenominador ( ) ;
57
58 return m;
59 }
60
61 public S t r i n g t o S t r i n g ( ) {
62 return p + ” / ” + q ;
63 }
64 }

Ejemplo 3.1: Clase implementa la abstracción de un número racional

Los constructores de las lı́neas 10-12 y 14-16, se apoyan del constructor


base de las lı́neas 18-23 para realizar la inicialización del objeto a través del
uso de la cláusula this. La cláusula this es una referencia que tienen todos
los objetos hacia sı́ mismos, por lo que en el contexto de los constructores,
46 CAPÍTULO 3. ESTRUCTURAS DE DATOS

Figura 3.2: Salida de la ejecución del Ejemplo 3.2

invoca al constructor sobre cargado que corresponda con el tipo y número de


argumentos que envı́a, que para el caso de las lı́neas 11 y 15, corresponden
con el constructor base de las lı́neas 18-23.
La implementación de las Ecuaciones 3.3 y 3.5 aparecen en las lı́neas 43-
50 y 52-59 respectivamente6 . Observe que en las lı́neas 46 y 47 (55 y 56) se
accede directamente a los atributos p y q a través del operador punto (.), es
decir, sin hacer uso de los métodos de acceso set y get; ésto es ası́ debido a que
el objeto s (m) es de la misma clase que define al método suma (multiplica),
por lo que el acceso es concedido7 .
Por otro lado, note también que para el objeto (this) que recibe el mensaje
suma (multiplica), los atributos p y q de las lı́neas 46 y 47 (55 y 56) son
accedidos sin ningún tipo de operador ni mensaje, es decir, son accedidos
por contexto, ya que el objeto que recibe el mensaje conoce cuáles son sus
propios atributos, por lo que dentro del método, la expresión:

p * r.obtenDenominador()

es equivalente a la expresión:

this.p * r.obtenDenominador()

Finalmente, el método toString (lı́neas 61-63) requiere una mención apar-


te, ya que éste método sobre escribe8 y redefine el comportamiento definido
en el método la clase Object del API de Java, y tiene como responsabilidad
el regresar una representación en cadena del objeto.
La clase de prueba del Ejemplo 3.1 es la del Ejemplo 3.2, cuya salida
corresponde a la mostrada en la Figura 3.2.
6
La implementación de las Ecuaciones 3.4 y 3.6 se dejan como ejercicio para el lector.
7
Recuerde lo planteado en el Ejercicio 2 del Capı́tulo 2 y analice la diferencia.
8
Razón por la cual se preservó el nombre del método.
3.4. ABSTRACCIÓN DE ESTRUCTURAS DE DATOS 47

1 /∗ C l a s e de p r u e b a para R a c i o n a l . Se crea n dos o b j e t o s


2 ( numeros r a c i o n a l e s ) , s e suman y m u l t i p l i c a n ( por medio de mensajes ) ,
3 y se presentan l o s r e s u l t a d o s correspondientes .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 public c l a s s P r u e b a R a c i o n a l {
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 R a c i o n a l r 1 = new R a c i o n a l ( 1 , 3 ) ; // 1/3
9 R a c i o n a l r 2 = new R a c i o n a l ( 2 , 5 ) ; // 2/5
10
11 System . out . p r i n t l n ( ” r1 = ” + r1 ) ;
12 System . out . p r i n t l n ( ” r2 = ” + r2 ) ;
13 System . out . p r i n t l n ( r 1 + ” + ” + r 2 + ” = ” + r 1 . suma ( r 2 ) ) ;
14 System . out . p r i n t l n ( r1 + ” ∗ ” + r2 + ” = ” + r1 . m u l t i p l i c a ( r2 ) ) ;
15 }
16 }

Ejemplo 3.2: Clase de prueba para la clase Racional


Por último, aunque el Ejemplo 3.2 se explica a sı́ mismo, se resaltarán los
siguientes aspectos:
1
Las lı́neas 8 y 9 crean los números racionales r1 y r2, donde r1 = 3
y
r2 = 25 .

Las lı́neas 13 y 14 envı́an los mensajes suma y multiplica respectiva-


mente, al objeto r1. Note que el argumento de ambos métodos es r2,
y que al valor de retorno de dichos métodos (un número racional), les
es enviado de manera implı́cita el mensaje toString para obtener la re-
presentación en cadena de los resultados correspondientes. Ésto último
sucede también con las lı́neas 11 y 12 pero para los objetos r1 y r2.

3.4. Abstracción de estructuras de datos


El estudio de las estructuras de datos implica, en general, dos propósitos
complementarios:

1. Identificar y desarrollar entidades y operaciones útiles relacionadas con


dichas entidades. También es necesario determinar el tipo de problemas
que se solucionan utilizando dichas entidades y operaciones.

2. El segundo es el de determinar representaciones para dichas entidades


abstractas, ası́ como implementar las operaciones abstractas en repre-
sentaciones concretas.
48 CAPÍTULO 3. ESTRUCTURAS DE DATOS

El primero de estos dos propósitos considera a un tipo de datos de alto


nivel como un instrumento que se usa para solucionar otros problemas, y
está en estrecha relación con la definición de tipos de datos abstractos (ADT).
El segundo propósito considera la implementación del tipo de dato o ADT,
como un problema que se va resolver utilizando objetos o elementos existentes
a través de la herencia (relación es (is-a)) o la composición (relación tiene
(has-a)).
Como ya se mencionó con anterioridad, en ocasiones ninguna implemen-
tación tanto de hardware como de software puede modelar por completo un
concepto matemático (un tipo de dato entero o real por ejemplo), por lo
que es importante conocer las limitaciones de una implementación particu-
lar, ya que una consideración fundamental de cualquier implementación es
su eficiencia.

3.4.1. Clases autorreferidas


La implementación de las estructuras de datos de los capı́tulos siguientes,
estará basada en un concepto muy importante: la autorreferencia.
En el contexto de la orientación a objetos, la autorreferencia es la ca-
pacidad que tienen los objetos de una clase de almacenar explı́citamente una
referencia a objetos de su misma clase, es decir, a objetos de su mismo tipo.
Considere el Ejemplo 3.3, el cual muestra una clase autorreferida (Nodo)
con dos atributos:

1. dato: el atributo que representa el elemento a almacenar en el nodo.

2. siguiente: el cual es una referencia a un objeto cuya clase es la misma


que lo define (Nodo), y por lo tanto, es una autorreferencia.

En estructuras de datos, un nodo es la unidad mı́nima de almacenamiento


dentro de la estructura de datos, y puede ser tan simple o elaborada como
se desee.
Para el caso del Ejemplo 3.3, el nodo únicamente almacena enteros primi-
tivos (int), pero como se discutirá más adelante, en el Capı́tulo 4, es posible
representar entidades más elaboradas que un número entero.
Considere la creación de un objeto nodo instanciado de la clase Nodo, y
su correspondiente representación mostrada en la Figura 3.3:

Nodo nodo = new Nodo(1974);


3.4. ABSTRACCIÓN DE ESTRUCTURAS DE DATOS 49

Figura 3.3: Representación de un objeto de la clase Nodo (Ejemplo 3.3)

En la Figura 3.3 puede apreciarse que un objeto es en realidad una re-


ferencia a una entidad con caracterı́sticas definidas por la clase de la que
deriva, que para el caso especı́fico del Ejemplo 3.3, están dadas por los atri-
butos dato y siguiente.
1 /∗ Ejemplo de una c l a s e a u t o r r e f e r i d a .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s Nodo{
5 private i n t dato ;
6 private Nodo s i g u i e n t e ;
7
8 Nodo ( i n t d ) {
9 dato = d ;
10 s i g u i e n t e = null ;
11 }
12
13 public void e s t a b l e c e D a t o ( i n t d ) {
14 dato = d ;
15 }
16
17 public i n t obtenDato ( ) {
18 return dato ;
19 }
20
21 public void e s t a b l e c e S i g u i e n t e ( Nodo n ) {
22 siguiente = n;
23 }
24
25 public Nodo o b t e n S i g u i e n t e ( ) {
26 return s i g u i e n t e ;
27 }
28 }

Ejemplo 3.3: Clase autorreferida


Observe la relación entre el Ejemplo 3.3 y la Figura 3.3, y note cómo
en el constructor (lı́neas 8-11) el objeto es inicializado con el valor recibi-
do como argumento, y con null para la referencia siguiente, lo cual ha sido
representado en la figura con una diagonal (\) para enfatizar que la referen-
cia representada por el atributo siguiente, no hace referencia inicialmente a
ningún otro objeto. Observe también que la clase o tipo del objeto nodo, es
50 CAPÍTULO 3. ESTRUCTURAS DE DATOS

Figura 3.4: Secuencia de nodos generados por una clase autorreferida

la misma que la del atributo siguiente, lo cual constituye la autorreferencia.


Asegúrese de comprender ésto antes de continuar.

3.4.2. Implementación
En base a lo descrito con anterioridad, es posible decir que las estructuras
de datos consisten, de manera general, en un conjunto de nodos ordenados
en una secuencia lógica como la que se muestra en la Figura 3.4.
El mecanismo utilizado para la inserción de nodos en la estructura de
datos, está en función directa de las reglas especificadas por la definición de
la estructura de datos.
Por otro lado, la especificación de las caracterı́sticas de la estructura de
datos se implementará a través de atributos, mientras que la especificación
de las reglas de operación o de comportamiento de la estructura de datos,
será implementada por medio de métodos.
Las estructuras de datos estudiadas en los capı́tulos siguientes, tendrán
en general la forma presentada en la Figura 3.4.

3.5. Consideraciones finales


Las nociones de la programación orientada a objetos son construidas sobre
las ideas de los tipos de datos abstractos.
Un ADT es una abstracción sumamente útil, y está estrechamente re-
lacionada con los principios de la orientación a objetos, ya que puede ser
definida en términos de las caracterı́sticas y los servicios que ofrece.
La importancia más significativa en la idea de los ADT es la separación
de las nociones de interfaz (servicios) e implementación.
La definición y operación de una estructura de datos, está en estrecha
relación con la definición de un ADT, por lo que en los capı́tulos siguientes,
3.5. CONSIDERACIONES FINALES 51

además de definir las estructuras de datos más usuales, se presentará una im-
plementación particular, pero es importante que el lector recuerde que existe
más de una implementación para una definición de un ADT determinado.
Finalmente, además de la implementación de las estructuras de datos, se
presentarán también algunas de las aplicaciones clave más representativas
en el desarrollo de cada una de las estructuras de datos estudiadas, con la
intención de poner en práctica, de manera combinada, tanto los conceptos
orientados a objetos, como los de las estructuras de datos.
52 CAPÍTULO 3. ESTRUCTURAS DE DATOS

3.6. Ejercicios
1. Investigue cómo es que se representan los tipos de datos primitivos en
una computadora.
Es preciso que conozca cómo se representa un tipo de datos int por
ejemplo, ¿cuátos bytes le son asignados a un entero?.
Los números de punto flotante (float y double por ejemplo), también
tiene una representación particular dentro de una computadora basada
en los conceptos de mantisa y exponente. Investigue dichos conceptos,
ası́ como la representación fı́sica de los tipos de datos en una compu-
tadora convencional.

2. Modifique el Ejemplo 3.2 para que genere una salida como la de la Fi-
gura 3.1. Asegúrese de probar el caso de error tanto para el constructor,
como para el método estableceDenominador.

3. Considere el Ejemplo 3.1, ¿qué valor inicial tiene el objeto Racional s


y m en las lı́neas 44 y 53 respectivamente?.

4. Modifique el Ejemplo 3.1 para que, en lugar de acceder directamente


a los atributos p y q de los objetos s y m (lı́neas 46-47 y 55-56 res-
pectivamente), se acceda a ellos y se modifiquen sus correspondientes
valores a través del método set correspondiente.

5. En base a lo descrito en el texto, modifique el Ejemplo 3.1 para que


cambie la sentencia (lı́nea 46):

s.p = p * r.obtenDenominador() + q * r.obtenNumerador();

por:

s.p = this.p * r.obtenDenominador() +


this.q * r.obtenNumerador();

Realice lo correspondiente para las lı́neas 47, 55 y 56 respectivamente.


3.6. EJERCICIOS 53

6. El Ejemplo 3.1 implementa las operaciones de adición (suma) y multi-


plicación (multiplica). Para que la implementación sea completa, imple-
mente las operaciones aritméticas faltantes de sustracción y división,
llámeles resta y divide respectivamente, y ajústese a la especificación
de las Ecuaciones 3.10 y 3.12 respectivamente.

7. Además de lo desarrollado en el ejercicio anterior, piense en cómo me-


jorar la definición del ADT racional discutido en el texto. Tome en
cuenta, al menos, los siguientes aspectos:

Comparación de igualdad: ¿cuándo se dice que dos números ra-


cionales son iguales?. Ejemplo:
3 1
≡ (3.7)
21 7
Simplificación: simplificar a su expresión mı́nima un número ra-
cional, ya sea como resultado de una operación, o simplemente
como utilidad. Ejemplo:
3 1
⇒ (3.8)
21 7
Investigue la definición y forma de dichos aspectos, analı́celos e im-
pleméntelos para completar y mejorar el Ejemplo 3.1.

8. Considere las siguientes definiciones para los números complejos:


Sean c1 y c2 dos números complejos es decir: c1 , c2 ∈ C definidos de la
siguiente manera:

c1 = (a + bi) y c2 = (c + di)

donde a, b, c, d ∈ R.
La suma de c1 y c2 se define como:

c1 + c2 = (a + bi) + (c + di) = (a + c) + (b + d)i (3.9)


y la resta de manera análoga:

c1 − c2 = (a + bi) − (c + di) = (a − c) + (b − d)i (3.10)


mientras que la multiplicación está dada por:
54 CAPÍTULO 3. ESTRUCTURAS DE DATOS

Figura 3.5: Representación de un objeto autorreferido

c1 ∗ c2 = (a + bi) ∗ (c + di) = (ac − bd) + (ad + bc)i (3.11)

La división es un poco más elaborada debido a que se racionaliza el


denominador, es decir, se multiplica el numerador y el denominador
por el conjugado del denominador9 :

c1 (a + bi) (a + bi) ∗ (c − di) (ac + bd) + (bc − ad)i


= = = (3.12)
c2 (c + di) (c + di) ∗ (c − di) c2 + d 2

Utilice las definiciones y operaciones anteriores para abstraer un ADT


complejo, y utilı́celas para realizar una implementación de dicha enti-
dad (como se hizo para el Ejemplo 3.1).
Escriba también una clase de prueba que permita probar la imple-
mentación del ADT complejo y las respectivas operaciones aritméticas
presentadas en las Ecuaciones 3.9, 3.10, 3.11 y 3.12.

9. En base a la explicación dada en la Sección 3.4.1 y tomando como


referencia la Figura 3.5, modifique el Ejemplo 3.3 de tal forma que
sobre cargue el constructor, para que, en caso de crear un objeto sin
argumentos:

Nodo nodo = new Nodo();

se construya un objeto como el mostrado en la Figura 3.5.


Utilice cero para el atributo dato, y asegúrese de probar su constructor.

9
El conjugado de un número complejo se obtiene cambiando el signo de su componente
imaginaria, es decir: si c = a + bi es un número complejo, su conjugado está dado por
c = a − bi.
Capı́tulo 4

Pilas

A whole stack of memories never equal one little hope.


Charles M. Schulz

I can’t predict how reading habits will change. But I will say that
the greatest loss is the paper archive - no more a great stack of
manuscripts, letters, and notebooks from a writer’s life, but only
a tiny pile of disks, little plastic cookies where once were
calligraphic marvels.
Paul Theroux

4.1. Definición
La pila es un objeto dinámico en constante cambio.
Una pila es un conjunto ordenado de elementos en el cual se pueden
insertar y eliminar elementos únicamente por un extremo: el tope de la pila.
La caracterı́stica más importante de una pila es que el último elemento
insertado en ella es el primero en eliminarse, mientras que el primero que fue
insertado, es el último en eliminarse. Por ésta razón, se dice que una pila es
una estructura de datos de tipo LIFO (Last In First Out).
El proceso de inserción y eliminación de elementos puede observarse en
la Figura 4.1, en donde se muestra, por medio de una flecha (→), la repre-
sentación del tope de la pila. Observe cómo con cada inserción o eliminación
se modifica el tope de la pila.
La representación mostrada en la Figura 4.1, permite visualizar todos los
elementos de la pila, sin embargo, es importante señalar que en un momento

55
56 CAPÍTULO 4. PILAS

Figura 4.1: Crecimiento y decrecimiento de una pila

dado, únicamente se tiene acceso al elemento que está siendo referido por el
tope de la pila, los demás elementos permanecen ocultos, tapados, por decirlo
de alguna manera, por el elemento que se encuentra en el tope de la pila.

4.1.1. Operaciones primitivas


Las operaciones primitivas o fundamentales definidas sobre una pila, son
la inserción (push) y la eliminación (pop).

1. El comportamiento definido para la operación push consiste en insertar


un nuevo elemento en la pila, mismo que constituirá el nuevo tope de
la pila.

2. El comportamiento definido para la operación pop elimina el elemento


referido por el tope de la pila, modificando en consecuencia el tope de
la pila, al elemento (si existe) que fue insertado inmediatamente antes
que el elemento eliminado.

Existen otras operaciones útiles al usar pilas, como por ejemplo, antes de
aplicar la operación pop a una pila, serı́a conveniente verificar que la pila no
esté vacı́a (operación ¿Está vacı́a?).
Otro comportamiento deseable serı́a la operación “ojeada”(peek), la cual
hecha un vistazo al elemento que se encuentra en el tope de la pila y lo regresa,
pero no lo elimina.
Las operaciones push, pop y peek, son muy comunes para la estructura de
datos pila, por lo que se conservarán dichos nombres en la implementación.
4.2. IMPLEMENTACIÓN 57

Figura 4.2: Abstracción de una pila como una secuencia de nodos

4.2. Implementación
La representación de una pila, como una secuencia de nodos, se muestra en
la Figura 4.2. Los detalles de su implementación se discutirán a continuación.

4.2.1. Pila primitiva


Esta sección describe la implementación de una pila primitiva. Se ha
denominado de ésta forma, debido a que implementa una estructura de datos
que almacena un tipo de dato primitivo: int.
La definición de la clase fundamental para la implementación de la es-
tructura de datos se muestra en el Ejemplo 4.1. Los detalles de este tipo de
implementación de clases de autorreferencia se han discutido con anteriori-
dad en la Sección 3.4.1 y no se repetirán aquı́, por lo que se sugiere al lector
que la revise nuevamente, y se tome el tiempo necesario para comparar el
Ejemplo 4.1 con el Ejemplo 3.3 antes de continuar.
1 /∗ C l a s e que p e r m i t e l a i n s t a n c i a c i o n de o b j e t o s ( nodos ) a u t o r r e f e r i d o s .
2 Cada nodo almacena un e n t e r o p r i m i t i v o y una r e f e r e n c i a a
3 o b j e t o s como e l .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 class NodoPrimitivo {
7 private i n t dato ;
8 private N o d o P r i m i t i v o s i g u i e n t e ;
9
10 NodoPrimitivo ( int d ) {
11 this (d , null ) ;
12 }
13
14 N o d o P r i m i t i v o ( i n t d , N o d o P r i m i t i v o nodo ) {
15 dato = d ;
16 s i g u i e n t e = nodo ;
17 }
18
19 public void e s t a b l e c e D a t o ( i n t d ) {
20 dato = d ;
21 }
22
58 CAPÍTULO 4. PILAS

23 public i n t obtenDato ( ) {
24 return dato ;
25 }
26
27 public void e s t a b l e c e S i g u i e n t e ( N o d o P r i m i t i v o n ) {
28 siguiente = n;
29 }
30
31 NodoPrimitivo o b t e n S i g u i e n t e ( ) {
32 return s i g u i e n t e ;
33 }
34 }

Ejemplo 4.1: Definición de la clase NodoPrimitivo


La implementación de la estructura de datos pila se muestra en el Ejem-
plo 4.2, y su explicación se centrará únicamente en los métodos push, pop,
estaVacia e imprime.
1 /∗ C l a s e que implementa una p i l a de o b j e t o s nodo de l a c l a s e N o d o P r i m i t i v o .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s P i l a P r i m i t i v a {
5 private N o d o P r i m i t i v o t o p e ;
6 private S t r i n g nombre ;
7
8 public P i l a P r i m i t i v a ( ) {
9 t h i s ( ” P i l a de e n t e r o s p r i m i t i v o s ” ) ;
10 }
11
12 public P i l a P r i m i t i v a ( S t r i n g n ) {
13 nombre = n ;
14 tope = null ;
15 }
16
17 // Metodo de i n s e r c i o n ( push ) de d a t o s a l a p i l a
18 public void push ( i n t e l e m e n t o ) {
19 i f ( estaVacia () )
20 t o p e = new N o d o P r i m i t i v o ( e l e m e n t o ) ;
21 else
22 t o p e = new N o d o P rim itiv o ( elemento , t o p e ) ;
23 }
24
25 // Metodo de e l i m i n a c i o n ( pop ) de d a t o s de l a p i l a
26 public i n t pop ( ) throws ExcepcionEDVacia {
27 i f ( estaVacia () )
28 throw new ExcepcionEDVacia ( nombre ) ;
29
30 i n t e l e m e n t o = t o p e . obtenDato ( ) ;
31 tope = tope . obtenSiguiente ( ) ;
32
33 return e l e m e n t o ;
34 }
35
36 // Metodo de v e r i f i c a c i o n de p i l a v a c i a ( e s t a V a c i a ?)
37 public boolean e s t a V a c i a ( ) {
4.2. IMPLEMENTACIÓN 59

38 return t o p e == n u l l ;
39 }
40
41 // Metodo de i m p r e s i o n de l o s e l e m e n t o s almacenados en l a p i l a
42 public void imprime ( ) {
43 i f ( estaVacia () )
44 System . out . p r i n t f ( ” Vacia : % s \n” , nombre ) ;
45 else {
46 System . out . p r i n t f ( ”La % s e s : ” , nombre ) ;
47 NodoPrimitivo a c t u a l = tope ;
48
49 while ( a c t u a l != n u l l ) {
50 // System . o u t . p r i n t f (” %s ” , a c t u a l . d a t a ) ;
51 System . out . p r i n t f ( ” % s ” , a c t u a l . obtenDato ( ) ) ;
52 // a c t u a l = a c t u a l . nextNode ;
53 actual = actual . obtenSiguiente () ;
54 }
55 System . out . p r i n t l n ( ) ;
56 }
57 }
58 }

Ejemplo 4.2: Definición de la clase PilaPrimitiva

El método estaVacia (lı́neas 37-39) realiza una verificación bastante sim-


ple: si el tope de la pila es igual a null, regresa verdadero (está vacı́a), si no,
regresa falso (existe al menos un elemento).
El método push por su parte (lı́neas 18-23), recibe como argumento el
elemento a insertar en la pila (elemento), y se apoya del método estaVacia y
de los constructores para realizar la inserción:

Si la pila está vacı́a (lı́nea 19), se crea un nuevo nodo con el elemento
correspondiente (lı́nea 20) para el atributo dato, y null en su atributo
siguiente.

Si la pila no está vacı́a (lı́nea 21), se crea un nuevo nodo con el elemento
correspondiente para el atributo dato, y con el tope actual en su atri-
buto siguiente (lı́nea 22). Ası́ mismo, note que el tope es actualizado
para hacer referencia al nodo recién creado.

Ahora bien, observe que el método pop (lı́neas 26-34) posee una carac-
terı́stica particular: el método lanza (throws) una excepción ExcepcionED-
Vacia (lı́nea 26). Ésto quiere decir que, en determinadas condiciones, el méto-
do puede lanzar una excepción; para el caso del método pop, dicha condición
consiste en intentar eliminar un elemento de la pila cuando ésta está vacı́a.
60 CAPÍTULO 4. PILAS

1 /∗ Ejemplo de d e f i n i c i o n de e x c e p c i o n .
2 La e x c e p c i o n s e r a l a n z a d a cuando s e haga un i n t e n t o de e l i m i n a c i o n
3 de una e s t r u c t u r a de d a t o s que e s t e v a c i a .
4 La c l a s e RuntimeException e s l a s u p e r c l a s e de l a s e x c e p c i o n e s
5 que pueden s e r l a n z a d a s d u r a n t e l a o p e r a c i o n normal de l a JVM.
6 @autor Ricardo Ruiz R o d r i g u e z
7 ∗/
8 public c l a s s ExcepcionEDVacia extends RuntimeException {
9 public ExcepcionEDVacia ( ) {
10 t h i s ( ” E s t r u c t u r a de d a t o s ” ) ;
11 }
12
13 public ExcepcionEDVacia ( S t r i n g s ) {
14 super ( s + ” v a c i a ” ) ;
15 }
16 }

Ejemplo 4.3: Definición de la clase ExcepcionEDVacia que se utiliza para la


implementación de todas las estructuras de datos

La excepción ExcepcionEDVacia definida en el Ejemplo 4.3 es en realidad


bastante simple, ya que delega toda la responsabilidad a RuntimeException
que es la clase de la que deriva, y lo único que hace es establecer un identifi-
cador para la excepción a través de sus constructores, los cuales en realidad
utilizan el constructor de la clase padre a través de la cláusula super. Es
importante que el lector recuerde esta excepción, ya que será la excepción
que utilizarán todas las estructuras de datos que se implementan en el libro.
Continuando con el Ejemplo 4.2, el método pop verifica (lı́nea 27) si la
pila está vacı́a, si lo está, crea y lanza la excepción correspondiente (lı́nea
28); en caso contrario, recupera el dato almacenado (lı́nea 30), y desecha
el nodo correspondiente al hacer que el tope haga ahora referencia al ele-
mento siguiente del tope actual (lı́nea 31), para finalmente regresar el dato
recuperado (lı́nea 33)1 .
Por último, en el método imprime (lı́neas 42-57), si la estructura de datos
está vacı́a (lı́nea 43), se reporta (lı́nea 44); en caso contrario, se realiza un
recorrido por todos los nodos de la estructura para imprimir su contenido
(lı́neas 47-54).
La clase de prueba para la pila primitiva del Ejemplo 4.2, se presenta en
el Ejemplo 4.4. Observe cómo en la lı́nea 6 se crea la pila y se utiliza un
constructor sin argumentos.
1
En Java no hay una eliminación o liberación explı́cita de memoria, dicha labor es
delegada al recolector de basura, el cual se encarga, grosso modo, de identificar aquellos
objetos que no estén siendo referidos, para entonces liberar la memoria que utilizan.
4.2. IMPLEMENTACIÓN 61

Figura 4.3: Inserción de elementos en la pila primitiva

1 /∗ C l a s e de p r u e b a para P i l a P r i m i t i v a .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s P r u e b a P i l a {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 P i l a P r i m i t i v a p i l a = new P i l a P r i m i t i v a ( ) ;
7
8 // Se i n s e r t a n d i e z e n t e r o s
9 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
10 p i l a . push ( i ) ;
11 p i l a . imprime ( ) ;
12 }
13 System . out . p r i n t l n ( ) ;
14
15 try {
16 // Se i n t e n t a e l i m i n a r once e n t e r o s
17 f o r ( i n t i = 0 ; i < 1 1 ; i ++){
18 i n t e l e m e n t o = p i l a . pop ( ) ;
19 System . out . p r i n t f ( ” Elemento e l i m i n a d o de l a p i l a : % d\n” ,
elemento ) ;
20 p i l a . imprime ( ) ;
21 }
22 } catch ( ExcepcionEDVacia e ) {
23 e . printStackTrace () ;
24 }
25 }
26 }

Ejemplo 4.4: Clase de prueba para la clase PilaPrimitiva

Las lı́neas 9-12 realizan la inserción en la pila de los números del cero al
nueve, y por cada inserción, se imprime todo el contenido de la pila, como se
muestra en la Figura 4.3.
Por otro lado, las lı́neas 15-24 realizan la eliminación de los elementos
de la pila. Dicho fragmento de código intenta eliminar once elementos de la
62 CAPÍTULO 4. PILAS

Figura 4.4: Eliminación de elementos de la pila primitiva

pila (lı́neas 17-18)2 , y dado que el método pop puede lanzar una excepción,
el código involucrado en la eliminación debe estar dentro de una cláusula
try-catch-finally, la cual permite atrapar (cachar) las excepciones que un
método pudiera lanzar.
Si se genera un excepción ExcepcionEDVacia, ésta es atrapada y el flujo
de control se envı́a al ámbito de la cláusula catch, en donde se realiza el
correspondiente tratamiento de la excepción. Para el caso del Ejemplo 4.4,
el manejo de la excepción consiste únicamente en imprimir la secuencia de
eventos (en forma de pila) que dieron lugar a la excepción, sin embargo, es
importante aclarar que el manejo de una excepción puede ser tan elaborado
como en un momento dado se requiera.
La salida correspondiente a la eliminación de elementos de la pila primi-
tiva, se muestra en la Figura 4.4.

2
Recuerde que sólo fueron insertados diez elementos, por lo que al intentar eliminar el
décimo primero, se generará la excepción ExcepcionEDVacia.
4.2. IMPLEMENTACIÓN 63

4.2.2. Pila genérica


Esta sección generaliza la implementación de una pila, de tal forma que
la estructura de datos tenga la capacidad de almacenar objetos genéricos, es
decir, objetos de cualquier clase.
Como primer paso, es preciso que el lector se tome el tiempo que considere
necesario, para comparar el Ejemplo 4.1 discutido en la sección anterior, con
el Ejemplo 4.5. Es importante resaltar que, aunque distintos, ambos ejemplos
son esencialmente equivalentes.
1 /∗ C l a s e que p e r m i t e l a i n s t a n c i a c i o n de o b j e t o s ( nodos ) a u t o r r e f e r i d o s .
2 Cada nodo almacena un o b j e t o g e n e r i c o T y una r e f e r e n c i a a
3 o b j e t o s como e l .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 c l a s s NodoG<T>{
7 private T dato ;
8 private NodoG<T> s i g u i e n t e ;
9
10 NodoG(T d ) {
11 this (d , null ) ;
12 }
13
14 NodoG(T d , NodoG<T> nodo ) {
15 dato = d ;
16 s i g u i e n t e = nodo ;
17 }
18
19 public void e s t a b l e c e D a t o (T d ) {
20 dato = d ;
21 }
22
23 public T obtenDato ( ) {
24 return dato ;
25 }
26
27 public void e s t a b l e c e S i g u i e n t e (NodoG<T> n ) {
28 siguiente = n;
29 }
30
31 NodoG<T> o b t e n S i g u i e n t e ( ) {
32 return s i g u i e n t e ;
33 }
34 }

Ejemplo 4.5: Definición de la clase para un nodo genérico (NodoG)

La primera diferencia que salta a la vista, además del nombre de la clase,


es la notación < T >. Dicha notación se utiliza en Java para especificar que la
clase gestiona objetos genéricos, es decir, que el tipo o la clase de los objetos
que almacena no está especificada en la definición de la misma, sino que se
64 CAPÍTULO 4. PILAS

especifica en el momento de la instanciación del objeto3 .


La lı́nea 7 del Ejemplo 4.5 define que la clase del atributo dato es T, es
decir, un genérico, y por lo tanto, el atributo siguiente es una referencia a un
objeto que almacena objetos genéricos.
Observe cómo ahora los constructores y los métodos de tipo set, reciben
como argumentos objetos genéricos T (lı́neas 10, 14 y 19), y referencias a
objetos que a su vez almacenan objetos genéricos (lı́neas 14 y 27); mientras
que los métodos de tipo get regresan genéricos (lı́nea 23), o referencias a
objetos que almacenan objetos genéricos (lı́nea 31).
1 /∗ C l a s e que implementa una p i l a de o b j e t o s nodo de l a c l a s e NodoG<T>.
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s P i l a <T>{
5 private NodoG<T> t o p e ;
6 private S t r i n g nombre ;
7
8 public P i l a ( ) {
9 this ( ” Pila generica ” ) ;
10 }
11
12 public P i l a ( S t r i n g n ) {
13 nombre = n ;
14 tope = null ;
15 }
16
17 // Metodo de i n s e r c i o n ( push ) de d a t o s a l a p i l a
18 public void push (T e l e m e n t o ) {
19 i f ( estaVacia () )
20 t o p e = new NodoG<T>( e l e m e n t o ) ;
21 else
22 t o p e = new NodoG<T>( elemento , t o p e ) ;
23 }
24
25 // Metodo de e l i m i n a c i o n ( pop ) de d a t o s de l a p i l a
26 public T pop ( ) throws ExcepcionEDVacia {
27 i f ( estaVacia () )
28 throw new ExcepcionEDVacia ( nombre ) ;
29
30 T e l e m e n t o = t o p e . obtenDato ( ) ;
31 tope = tope . obtenSiguiente ( ) ;
32
33 return e l e m e n t o ;
34 }
35
36 // Metodo de v e r i f i c a c i o n de p i l a v a c i a ( e s t a V a c i a ?)
37 public boolean e s t a V a c i a ( ) {
38 return t o p e == n u l l ;
39 }
40
41 // Metodo de i m p r e s i o n de l o s e l e m e n t o s almacenados en l a p i l a

3
Observe la lı́nea 6 del Ejemplo 4.7.
4.2. IMPLEMENTACIÓN 65

42 public void imprime ( ) {


43 i f ( estaVacia () )
44 System . out . p r i n t l n ( ” Vacia : ” + nombre ) ;
45 else {
46 System . out . p r i n t ( ”La ” + nombre + ” e s : ” ) ;
47 NodoG<T> a c t u a l = t o p e ;
48
49 while ( a c t u a l != n u l l ) {
50 System . out . p r i n t ( a c t u a l . obtenDato ( ) + ” ” ) ;
51 actual = actual . obtenSiguiente () ;
52 }
53 System . out . p r i n t l n ( ) ;
54 }
55 }
56 }

Ejemplo 4.6: Definición de la clase Pila que permite almacenar objetos


genéricos
Ahora bien, las consideraciones hechas para el Ejemplo 4.5 respecto a
los genéricos, son las mismas que debe tomar en cuenta para el Ejemplo
4.6, por lo que una vez más, se pide encarecidamente al lector que compare
éste último con el Ejemplo 4.2 discutido en la sección anterior, tomando en
consideración lo explicado hasta este momento para los genéricos.
1 /∗ C l a s e de p r u e b a para P i l a .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s P r u e b a P i l a G e n e r i c a {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 P i l a <I n t e g e r > p i l a = new P i l a <I n t e g e r >() ;
7
8 // Se i n s e r t a n d i e z e n t e r o s
9 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
10 p i l a . push ( i ) ;
11 p i l a . imprime ( ) ;
12 }
13 System . out . p r i n t l n ( ) ;
14
15 try {
16 // Se i n t e n t a e l i m i n a r once e n t e r o s
17 f o r ( i n t i = 0 ; i < 1 1 ; i ++){
18 I n t e g e r e l e m e n t o = p i l a . pop ( ) ;
19 System . out . p r i n t l n ( ” Elemento e l i m i n a d o de l a p i l a : ” +
elemento ) ;
20 p i l a . imprime ( ) ;
21 }
22 } catch ( ExcepcionEDVacia e ) {
23 e . printStackTrace () ;
24 }
25 }
26 }

Ejemplo 4.7: Clase de prueba para la pila genérica


66 CAPÍTULO 4. PILAS

Observe que en ninguna parte del Ejemplo 4.6 se hace explı́cita una clase
especı́fica para el genérico T, por lo que también aquı́ se hace una gestión de
genéricos en directa relación con los genéricos utilizados en el Ejemplo 4.5.
En resumen, la clase NodoG del Ejemplo 4.5 define nodos que almacenan
objetos genéricos (nodos genéricos), los cuales son utilizados de manera con-
veniente por la clase Pila del Ejemplo 4.6, para conformar una estructura de
datos dinámica que almacena objetos o nodos genéricos en forma de pila.
Finalmente, el Ejemplo 4.7 muestra la clase de prueba para la pila genérica
del Ejemplo 4.6. La lı́nea más importante del Ejemplo 4.7 es la lı́nea 6, ya que
en ella es en donde finalmente se define la clase de objetos que contendrá la
pila: Integer.
Consulte la Sección A.5.6 del Apéndice A para ampliar un poco más la
información y los detalles acerca de los genéricos en Java.
Adicionalmente, asegúrese de comprobar que la salida del Ejemplo 4.7
corresponde, en esencia, con la de la Figura 4.3 para la inserción de datos en
la pila (push), y con la de la Figura 4.4 para la eliminación (pop).

4.3. Aplicaciones
A continuación se presentan algunas de las aplicaciones más representa-
tivas de una pila, la selección presentada dista por mucho, de una selección
completa.

4.3.1. Análisis básico de expresiones


Considere la Expresión 4.1:
x+y
x× j−3
+y
7− 5 (4.1)
4− 2
Se insta al lector a que sea tan amable de contestar una por una, las
siguientes preguntas:

¿Es clara? es decir ¿Se entiende?

Para valores concretos de x, y, y j ¿Podrı́a evaluarla?

¿Puede escribir la misma expresión en una sola lı́nea de texto, como lo


harı́a para un programa?
4.3. APLICACIONES 67

La representación “lineal”de la Expresión 4.1 se muestra en la Expresión


4.2:

7–((x ∗ ((x + y)/(j − 3)) + y)/(4–5/2)) (4.2)


Observe que la Expresión 4.2 contiene paréntesis, los cuales son indis-
pensables para agrupar de manera apropiada las operaciones involucradas,
mientras que la representación de la Expresión 4.1 no los tiene, ya que son,
en principio, innecesarios. Ahora bien, en base a lo anterior, considere lo
siguiente:
¿Cómo saber que la Expresión 4.2 está correctamente balanceada en
cuanto a paréntesis se refiere, de tal forma que represente exactamente
lo mismo que la Expresión 4.1?
¿Y si la Expresión 4.1 fuera más grande y/o más compleja?
Considere ahora la Expresión 4.3

{x + (y − [a + b] × c) − [(d + e)]}
(4.3)
(h − (j − (k − [l − n])))
Cuya representación “lineal” está dada por la Expresión 4.4:

{x + (y–[a + b]) ∗ c–[(d + e)]}/(h–(j–(k–[l − n]))). (4.4)


Al igual que antes:
¿Cómo saber si la Expresión 4.4 está correctamente balanceada en
cuanto a sı́mbolos de agrupación se refiere? Note que ahora se han
introducido otros sı́mbolos de agrupación de expresiones (corchetes y
llaves) además de los paréntesis.
Adicionalmente ¿Cómo saber que un sı́mbolo de agrupación está cerran-
do a su correspondiente sı́mbolo de apertura?, es decir ¿Cómo saber que
los sı́mbolos de agrupación están correctamente asociados?
Deberı́a resultar claro, que es mucho más fácil para las personas compren-
der expresiones denotadas como en 4.1 o en 4.3; sin embargo, las representa-
ciones “lineales”son las que se utilizan en los lenguajes de programación, por
lo que se requiere de un mecanismo que verifique, de manera automática, que
una expresión esté bien escrita, al menos en cuanto a sı́mbolos de agrupación
se refiere, para ello, considere el siguiente pseudocódigo:
68 CAPÍTULO 4. PILAS

valida = true;
p = pila vacı́a;

while(no sea fin de cadena){


procesar el siguiente sı́mbolo de la cadena;

if(sı́mbolo == ‘(’ || sı́mbolo == ‘[’ || sı́mbolo == ‘{’)


p.push(sı́mbolo);
else if(sı́mbolo == ‘)’ || sı́mbolo == ‘]’ ||
sı́mbolo == ‘}’){
if(p.estaVacia())
valida = false;
else{
Char s = p.pop();
if(s no es equivalente a sı́mbolo)
valida = false;
}
}
} // while

if(!p.estaVacia())
valida = false;

if(valida)
println("La expresión es correcta y está balanceada.");
else
println("Existe error en los sı́mbolos de agrupación.");

Algoritmo 4.1: Verificación de balanceo

Dada una expresión almacenada en una cadena, el Algoritmo 4.1 utiliza


una pila para realizar la verificación de los sı́mbolos de agrupación que se
encuentren en dicha expresión.
Se deja como ejercicio para el lector realizar una implementación de dicho
algoritmo, ası́ como la resolución de los aspectos inherentes a la equivalencia
de sı́mbolos4 .
4
Vea el Ejercicio 7.
4.3. APLICACIONES 69

4.3.2. Notación interfija, postfija y prefija


Considere la suma de dos números cualesquiera A y B. Aplicar el operador
“+” a los operandos A y B, y representar la suma como A + B, tiene el
nombre de representación o notación interfija.
La notación interfija, aunque es la más común, no es la única. Existen
al menos otras dos notaciones alternativas para expresar la suma de A y B
utilizando los mismos sı́mbolos A, B y +:

1. + A B representación o notación prefija.

2. A B + representación o notación postfija.

Los prefijos “pre”, “pos” e “inter” hacen referencia a la posición relativa


del operador en relación a los operandos.
Una función en un lenguaje de programación gobernado por el paradigma
estructurado, como el lenguaje C por ejemplo, utiliza notación prefija5 , ya
que el operador suma precede a los operandos.

Conversión de interfija a postfija


Considere la Expresión 4.5:

A+B×C (4.5)
la cual implı́citamente indica:

A + (B × C) (4.6)
Suponga que se desea convertir la Expresión 4.6 a su representación en
postfija ¿Cómo proceder?
Los ejemplos siguientes asumen una representación “lineal”de las expre-
siones. En base a lo anterior, es importante que el lector tenga presente que
el proceso de conversión se basa en las reglas de precedencia de los operadores
involucrados en la expresión a convertir.
Ası́, para convertir una expresión de su notación interfija a su notación
postfija, se tienen lo siguientes pasos:
5
Considere por ejemplo la suma de dos números enviados a una función invocada me-
diante suma(a, b).
70 CAPÍTULO 4. PILAS

1. A + (B × C) forma interfija.

2. A + (BC×) se convierte la multiplicación, y el nuevo elemento se con-


sidera como un solo número6 .

3. A(BC×)+ se convierte la suma con las mismas consideraciones expues-


tas en el punto anterior.

4. ABC × + se eliminan paréntesis para obtener la representación final


en postfija.

Con base en lo anterior, es posible afirmar que las dos reglas que se siguen
durante el proceso de conversión a postfija son las siguientes:

1. Las operaciones con mayor precedencia se convierten primero.

2. Después de que una parte de la expresión se ha convertido a notación


postfija, ésta se considerará como un operando único.

Ejemplo
Dada la Expresión 4.7 (note que no es la misma que la Expresión 4.6):

(A + B) × C (4.7)
convierta dicha expresión a su notación postfija.
En base a lo expuesto con anterioridad, el proceso de solución está dado
por los siguientes pasos:

1. (A + B) × C forma interfija.

2. (AB+) × C se convierte la adición.

3. (AB+)C× se convierte la multiplicación.

4. AB + C× se eliminan paréntesis: representación postfija.


6
De hecho, después de aplicar el operador, el resultado (producto) es en efecto, otro
número.
4.3. APLICACIONES 71

Conversión de interfija a prefija


Las reglas para convertir una expresión de su notación interfija a su no-
tación prefija, son idénticas a las de conversión de interfija a postfija. El
único cambio a considerar, es que ahora el operador se coloca antes de los
operandos, en lugar de colocarlo después de ellos.

Aspectos a considerar
Finalmente, respecto a las notaciones prefija y postfija cabe hacer mención
de un par de consideraciones:

1. La representación prefija no siempre es una imagen reflejo de la repre-


sentación postfija.

2. El orden de los operadores en las expresiones postfijas determina el or-


den real de las operaciones al evaluar la expresión, haciendo innecesario
el uso de paréntesis.

En la sección de Ejercicios tendrá oportunidad de practicar y de ampliar


su experiencia al respecto.

4.3.3. Evaluación de expresiones


Aunque para las personas en general es más fácil y natural comprender
las expresiones interfijas7 , para el procesamiento de expresiones por medio
de una computadora no lo es.
Considere lo siguiente: dada una expresión en notación interfija con valo-
res especı́ficos ¿Cómo evaluarı́a algorı́tmicamente dicha expresión? Piense y
reflexione en ello antes de continuar.
Ahora bien, con valores especı́ficos para una expresión en notación post-
fija ¿Cómo evaluarı́a algorı́tmicamente dicha expresión? Para ésto último,
considere el siguiente algoritmo:
7
Quizá porque en su mayorı́a ası́ fuimos instruidos y ası́ estamos acostumbrados, pero
¿Qué pasarı́a si desde pequeños, en lugar de haber aprendido a realizar operaciones utili-
zando notación interfija, se nos hubiera enseñado a realizarlas utilizando notación prefija
por ejemplo? ¿Qué serı́a entonces lo fácil y natural de comprender?
72 CAPÍTULO 4. PILAS

pilaOperandos = pila vacı́a;

while(no sea fin de cadena){


sı́mbolo = siguiente carácter de la cadena;
if(sı́mbolo es un operando)
pilaOperandos.push(sı́mbolo);
else{
numero2 = pilaOperandos.pop();
numero1 = pilaOperandos.pop();
resultado = numero1 sı́mbolo numero2;
pilaOperandos.push(resultado);
}
}
return pilaOperandos.pop();

Algoritmo 4.2: Evaluación de una expresión en notación postfija

La solución propuesta por el Algoritmo 4.2 se basa, como es de esperarse,


en una pila. Se deja como ejercicio al lector el análisis y comprensión de dicho
algoritmo, ası́ como su correspondiente implementación8 .

4.4. Consideraciones finales


La pila es una estructura de datos sumamente importante y ampliamente
utilizada en distintas áreas no sólo de la computación. Su definición y fun-
cionalidad es clara y simple, por lo que no es casual que haya sido la primera
estructura de datos estudiada y presentada en el libro.
Por otro lado, los conceptos expuestos en el texto para los genéricos en
Java, resultarán fundamentales para los siguientes capı́tulos, ya que todas
las estructuras de datos siguientes se basan en ellos, por lo que se invita
nuevamente al lector a realizar un repaso de los conceptos relacionados con
los genéricos, ası́ como a tener presente la importancia de los mismos a lo
largo de los capı́tulos restantes.
Adicionalmente, una vez que se han presentado los conceptos de pila
(definición y operaciones) y una propuesta de implementación, resulta fun-
8
Consulte la Sección de Ejercicios para obtener mayor información. En particular, revise
a partir del Ejercicio 8.
4.4. CONSIDERACIONES FINALES 73

Figura 4.5: Diagrama de clases UML para la pila genérica

damental el conocer la relación que existe entre las clases más importantes
involucradas en la implementación de la pila genérica estudiada. El diagrama
de clases UML de la Figura 4.5 presenta dicha relación.
Insto amablemente al lector a que se tome el tiempo que considere necesa-
rio para comparar el diagrama de la Figura 4.5 con las clases que implementan
la pila genérica (Ejemplo 4.6), el nodo genérico (Ejemplo 4.5), y la excepción
de estructura de datos vacı́a (Ejemplo 4.3).
Los detalles de UML quedan fuera de los alcances del libro; sin embargo, el
diagrama de clases UML la Figura 4.5 muestra una relación de composición
entre la clase Pila y la clase NodoG de cero a muchos (0..∗), lo cual quiere
decir que una pila puede tener ninguno, o muchos nodos. Por otro lado,
también se muestra la relación de asociación uno a uno existente entre la
clase Pila y la clase ExcepcionEDVacia 9 .
Asegúrese de comprender el diagrama UML de la Figura 4.5 ası́ como
su relación con los ejemplos citados, ya que en los capı́tulos siguientes, se
utilizarán este tipo de diagramas de clases UML en complemento con la
definición del ADT, para definir la implementación de la estructura de datos
correspondiente.

9
Observe que el diagrama también muestra la relación de herencia entre la clase Ex-
cepcionEDVacia y la clase RuntimeException del API de Java.
74 CAPÍTULO 4. PILAS

4.5. Ejercicios
1. En el Ejemplo 4.2, el método imprime tiene las lı́neas 50 y 52 como
comentarios ¿Qué sucede si sustituye la lı́nea 51 por la lı́nea 50, y la
lı́nea 53 por la lı́nea 52? ¿Compilará? ¿Si sı́ por qué, y si no por qué?
Si compila ¿Cuál será el resultado de la ejecución?
Determine sus respuestas y después corrobore las mismas con la expe-
rimentación.
2. En el Ejemplo 4.4 se creó una pila utilizando un constructor sin ar-
gumentos. Modifique dicho ejemplo para asignar un nuevo nombre a
la pila por medio del constructor correspondiente, ası́gnele a la pila el
nombre de “Mi primera pila”, recompile y pruebe su funcionamiento.
3. En el texto, durante la explicación del Ejemplo 4.4, se menciona la
cláusula try-catch-finally, sin embargo, en dicho ejemplo sólo se mues-
tra el uso de la cláusula try-catch. Investigue y documéntese acerca
del uso y funcionamiento de la cláusula completa try-catch-finally.
También investigue más acerca del uso y manejo de excepciones, ası́ co-
mo la documentación del API respecto al método printStackTrace.
4. Modifique el Ejemplo 4.6 para que:
a) Agregue el método peek a la implementación. Recuerde que dicha
operación hecha un vistazo al elemento que se encuentra en el tope
de la pila y lo regresa, pero no lo elimina.
b) Incorpore un atributo privado numérico (n), que lleve el control
del número de elementos insertados en la pila. Al respecto no
olvide:
1) Inicializar explı́citamente dicho atributo a cero en el construc-
tor.
2) Proporcionar únicamente el método de tipo get para el atri-
buto n: obtenN().
5. Tomando como referencia el Ejemplo 4.7, modifı́quelo para que la pila
genérica del Ejemplo 4.6 almacene otro tipo de objetos además de los
de la clase Integer. Pruebe con al menos las siguientes clases:
Float.
4.5. EJERCICIOS 75

String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).

6. Utilice una pila para verificar si, dada una expresión, ésta es o no un
palı́ndromo10 .
Algunos ejemplos de palı́ndromos son:

1991
2002
Se van sus naves.
Ateo por Arabia iba raro poeta.
Dábale arroz a la zorra el abad.
Anita lava la tina.
La ruta nos aportó otro paso natural.
Las Nemocón no comen sal.
No di mi decoro, cedı́ mi don.
A la catalana banal, atácala.

Nota: Simplifı́quese la vida y no considere acentos ni la letra ñ en su


implementación.

7. Realice la implementación del Algoritmo 4.1 presentado en la Sección


4.3.1. Para lo anterior, construya una clase cuyo nombre sea Expresion,
e implemente dicho algoritmo como uno de los servicios o acciones de
la clase, identifique (nombre) al método como verificaBalance.
No olvide realizar las pruebas correspondientes para validar y verificar
la funcionalidad de su propuesta.
Sugerencia: implemente la verificación de equivalencia de sı́mbolos
como un método privado, es decir, un método que proporcione servicio
a otros métodos de la misma clase (servicio interno), pero no a los
objetos instanciados (servicios públicos).
10
Un palı́ndromo es una frase o expresión que se lee o interpreta igual procesándola de
izquierda a derecha, que de derecha a izquierda.
76 CAPÍTULO 4. PILAS

8. Convierta paso a paso las siguientes expresiones en su representación


en interfija, a su correspondiente notación postfija:

a) A + B
b) A + B − C
c) (A + B) × (C − D)
d ) A@B × C–D + E/F/(G + H)
e) ((A + B) × C–(D − E))@(F + G)
f ) A–B/(C × D@E)

Nota: El sı́mbolo @ representa la potencia, y es el de mayor preceden-


cia.
Solución:

a) AB+
b) AB + C−
c) AB + CD − ×
d ) AB@C × D − EF/GH + /+
e) AB + C × DE − −F G + @
f ) ABCDE@ × /−

9. Convierta paso a paso las expresiones del ejercicio anterior a su repre-


sentación en notación prefija.
Solución:

a) +AB
b) − + ABC
c) × + AB − CD
d ) + − ×@ABCD//EF + GH
e) @ − × + ABC − DE + F G
f ) −A/B × C@DE

10. Diseñe un algoritmo, ası́ como su correspondiente implementación, para


convertir una expresión en notación interfija en su representación:
4.5. EJERCICIOS 77

a) Postfija (método obtenPostfija).


b) Prefija (método obtenPrefija).

Agregue los métodos obtenPostfija y obtenPrefija a la clase Expresion


iniciada en el Ejercicio 7. Asegúrese de probar sus implementaciones
con, al menos, los dos ejercicios anteriores.

11. Realice la implementación del Algoritmo 4.2. Implemente dicho algo-


ritmo como uno de los servicios de la clase Expresion iniciada en el
Ejercicio 7, identifique (nombre) al método como evaluaPostfija.
No olvide realizar las pruebas correspondientes para validar y verificar
la funcionalidad de su propuesta.
Sugerencia: implemente la realización de la operación representada
por sı́mbolo, como un método privado, es decir, un método que pro-
porcione servicio a otros métodos de la misma clase (servicio interno),
pero no a los objetos instanciados (servicios públicos).
78 CAPÍTULO 4. PILAS
Capı́tulo 5

Colas de espera

An Englishman, even if he is alone, forms an orderly queue of


one.
George Mikes

5.1. Definición
Una cola de espera o simplemente cola, es un conjunto ordenado de
elementos del que se pueden eliminar dichos elementos de un extremo (llama-
do inicio de la cola), y en el que pueden insertarse elementos en el extremo
opuesto (llamado fin de la cola).
El primer elemento insertado en una cola es el primer elemento en ser
eliminado, mientras que el último elemento insertado, también es el último
en ser eliminado. Por ésta razón, se dice que una cola es una estructura de
datos de tipo FIFO (First In First Out).
En el mundo real abundan ejemplos de este tipo de estructuras:

Lı́neas aéreas.

Lı́neas de autobuses.

Colas de espera en los supermercados.

Colas de espera en los bancos.

Etcétera, etcétera, etcétera.

79
80 CAPÍTULO 5. COLAS DE ESPERA

Figura 5.1: Inserción y eliminación de elementos en una cola de espera

La Figura 5.1 (a) muestra una representación de una cola de espera que
permite visualizar los elementos almacenados en la misma.
La eliminación de uno de los elementos (A) se muestra en la Figura 5.1
(b), mientras que la inserción de los elementos D y E se muestra en la Figura
5.1 (c). Observe también cómo las referencias al inicio y fin de la cola son
modificadas en cada inserción y eliminación.

5.1.1. Operaciones primitivas


Se definen tres operaciones primitivas sobre una cola de espera:

1. La operación inserta, agrega un elemento en la parte final de la cola.


También se dice que esta operación forma o enlista un elemento a la
cola.

2. La operación elimina suprime un elemento de la parte inicial de la


cola. Esta operación despacha o atiende al elemento que se encuentra
al inicio de la cola.

3. La operación estaVacia1 regresa verdadero o falso dependiendo de si


la cola de espera, contiene o no elementos respectivamente.

Una operación deseable para una cola de espera, serı́a la de imprimir los
elementos de la cola, la cual se denotará como imprime.
1
Esta operación fue definida como opcional o deseable para la pila. Para el caso de la
cola, dicha operación es ahora una primitiva, por lo que su implementación es obligatoria.
5.1. DEFINICIÓN 81

Figura 5.2: Abstracción de una cola de espera como una secuencia de nodos

Figura 5.3: Diagrama de clases UML para la cola de espera

5.1.2. Representación
La representación mostrada en la Figura 5.2, es una abstracción de la
implementación que se realizará en la siguiente sección.
Observe cómo la Figura 5.2 luce muy similar a la Figura 4.2 del Capı́tulo
4. Sin embargo, la Figura 5.2 muestra el uso de dos referencias: inicio y
fin, las cuales denotan respectivamente, el primero y último de los elementos
almacenados en la cola de espera. Note que, para el caso de un solo elemento,
dichas referencias harán referencia, valga la redundancia, al mismo nodo.
Por otro lado, el diagrama de clases UML presentado en la Figura 5.3,
muestra el diseño de clases de la cola de espera que se desea implementar.
Dicho diseño complementa, en consecuencia, la definición de dicha estructura
de datos. A su vez, el diagrama de clases UML muestra también, la relación
que existe entre las clases más importantes que se involucrarán durante la
implementación de la cola de espera.
Finalmente, el diagrama de la Figura 5.3 muestra también la reutilización
de las clases NodoG y ExcepcionEDVacia, lo cual, no lo olvide, es también una
de las caracterı́sticas más importantes de la orientación a objetos. Tómese el
lector el tiempo necesario para revisar y analizar el diagrama de clases, antes
de avanzar hacia la implementación.
82 CAPÍTULO 5. COLAS DE ESPERA

5.2. Implementación
El nodo genérico definido en la clase del Ejemplo 5.1 ya ha sido presentado
con anterioridad en el Capı́tulo 4 (Ejemplo 4.5) y no se explicará nuevamente.
Dicho ejemplo sólo se ha incluido aquı́ como referencia inmediata al lector,
para facilitarle la relación, y la completa comprensión de la implementación
de la cola de espera.
1 /∗ C l a s e que p e r m i t e l a i n s t a n c i a c i o n de o b j e t o s ( nodos ) a u t o r r e f e r i d o s .
2 Cada nodo almacena un o b j e t o g e n e r i c o T y una r e f e r e n c i a a
3 o b j e t o s como e l .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 c l a s s NodoG<T>{
7 private T dato ;
8 private NodoG<T> s i g u i e n t e ;
9
10 NodoG(T d ) {
11 this (d , null ) ;
12 }
13
14 NodoG(T d , NodoG<T> nodo ) {
15 dato = d ;
16 s i g u i e n t e = nodo ;
17 }
18
19 public void e s t a b l e c e D a t o (T d ) {
20 dato = d ;
21 }
22
23 public T obtenDato ( ) {
24 return dato ;
25 }
26
27 public void e s t a b l e c e S i g u i e n t e (NodoG<T> n ) {
28 siguiente = n;
29 }
30
31 NodoG<T> o b t e n S i g u i e n t e ( ) {
32 return s i g u i e n t e ;
33 }
34 }

Ejemplo 5.1: Nodo genérico utilizado en la cola de espera

De manera análoga, el Ejemplo 5.2 muestra la excepción utilizada por


la implementación de la cola. Dicha clase también ha sido explicada con
anterioridad en el Ejemplo 4.3.
Se invita al lector a revisar estos dos ejemplos iniciales como preámbulo
a la implementación de la cola de espera. En caso de que surgiera alguna
duda, consulte las secciones correspondientes a los ejemplos mencionados en
5.2. IMPLEMENTACIÓN 83

los párrafos anteriores.


1 /∗ Ejemplo de d e f i n i c i o n de e x c e p c i o n .
2 La e x c e p c i o n s e r a l a n z a d a cuando s e haga un i n t e n t o de e l i m i n a c i o n
3 de una e s t r u c t u r a de d a t o s que e s t e v a c i a .
4 La c l a s e RuntimeException e s l a s u p e r c l a s e de l a s e x c e p c i o n e s
5 que pueden s e r l a n z a d a s d u r a n t e l a o p e r a c i o n normal de l a JVM.
6 @autor Ricardo Ruiz R o d r i g u e z
7 ∗/
8 public c l a s s ExcepcionEDVacia extends RuntimeException {
9 public ExcepcionEDVacia ( ) {
10 t h i s ( ” E s t r u c t u r a de d a t o s ” ) ;
11 }
12
13 public ExcepcionEDVacia ( S t r i n g s ) {
14 super ( s + ” v a c i a ” ) ;
15 }
16 }

Ejemplo 5.2: Excepción utilizada en la cola de espera


La implementación de la cola de espera se muestra en el Ejemplo 5.3. An-
tes de comenzar con la explicación de los métodos que implementan las ope-
raciones primitivas, insto amablemente al lector a que compare el diagrama
de clases de la Figura 5.3, con el código del Ejemplo 5.3, ésto con la intención
de que identifique la relación que existe entre el diseño y la implementación,
representados por el diagrama de clases, y el código respectivamente.
La identificación y comprensión de atributos y constructores deberı́an
resultar totalmente claros para el lector, excepto quizá por el modificador
de nivel de acceso protected, el cual hace que el atributo correspondiente
sea accesible únicamente por las clases del mismo paquete, o por subclases
de la clase que lo define. Dicho lo anterior, la explicación del Ejemplo 5.3
iniciará con los siguientes métodos2 :

estaVacia (lı́neas 47-49) realiza una verificación bastante simple: si ini-


cio es igual a null, regresa verdadero (la cola está vacı́a), si no, regresa
falso (existe al menos un elemento).

imprime (lı́neas 52-55), si la estructura de datos está vacı́a (lı́nea 53), se


reporta (lı́nea 54); en caso contrario, se realiza un recorrido por todos
los nodos de la estructura para imprimir su contenido (lı́neas 57-62).

2
Los métodos estaVacia e imprime fueron descritos en la implementación de la pila
genérica del Ejemplo 4.6, y de hecho, son idénticos excepto por una pequeña diferencia.
Serı́a un buen ejercicio para el lector que la identificara.
84 CAPÍTULO 5. COLAS DE ESPERA

1 /∗ C l a s e que implementa una c o l a de e s p e r a de o b j e t o s nodo g e n e r i c o s


2 de l a c l a s e NodoG<T>.
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s Cola<T>{
6 protected NodoG<T> i n i c i o ;
7 protected NodoG<T> f i n ;
8 private S t r i n g nombre ;
9
10 public Cola ( ) {
11 t h i s ( ” Cola de e s p e r a ” ) ;
12 }
13
14 public Cola ( S t r i n g n ) {
15 nombre = n ;
16 i n i c i o = f i n = null ;
17 }
18
19 // Metodo de i n s e r c i o n de d a t o s en l a c o l a de e s p e r a
20 public void i n s e r t a (T e l e m e n t o ) {
21 i f ( estaVacia () )
22 i n i c i o = f i n = new NodoG<T>( e l e m e n t o ) ;
23 else {
24 // f i n = f i n . s i g u i e n t e = new NodoG<T>( e l e m e n t o ) ;
25 f i n . e s t a b l e c e S i g u i e n t e (new NodoG<T>( e l e m e n t o ) ) ;
26 fin = fin . obtenSiguiente () ;
27 }
28 }
29
30 // Metodo de e l i m i n a c i o n de d a t o s de l a c o l a de e s p e r a
31 public T e l i m i n a ( ) throws ExcepcionEDVacia {
32 i f ( estaVacia () )
33 throw new ExcepcionEDVacia ( nombre ) ;
34
35 T e l e m e n t o = i n i c i o . obtenDato ( ) ;
36
37 // a c t u a l i z a r e f e r e n c i a s para i n i c i o y f i n
38 i f ( i n i c i o == f i n )
39 i n i c i o = f i n = null ;
40 else
41 i n i c i o = i n i c i o . obtenSiguiente () ;
42
43 return e l e m e n t o ;
44 }
45
46 // Metodo de v e r i f i c a c i o n de c o l a de e s p e r a v a c i a ( e s t a V a c i a ?)
47 public boolean e s t a V a c i a ( ) {
48 return i n i c i o == n u l l ;
49 }
50
51 // Metodo de i m p r e s i o n de l o s e l e m e n t o s almacenados en l a c o l a de e s p e r a
52 public void imprime ( ) {
53 i f ( estaVacia () )
54 System . out . p r i n t l n ( ” Vacia : ” + nombre ) ;
55 else {
56 System . out . p r i n t ( ”La ” + nombre + ” e s : ” ) ;
5.2. IMPLEMENTACIÓN 85

57 NodoG<T> a c t u a l = i n i c i o ;
58
59 while ( a c t u a l != n u l l ) {
60 System . out . p r i n t ( a c t u a l . obtenDato ( ) + ” ” ) ;
61 actual = actual . obtenSiguiente () ;
62 }
63 System . out . p r i n t l n ( ) ;
64 }
65 }
66 }

Ejemplo 5.3: Definición de la clase Cola que permite almacenar objetos


genéricos
Los dos métodos restantes corresponden con las dos operaciones primiti-
vas que fueron definidas, y se describen a continuación:
1. El método inserta verifica, en la lı́nea 21, si la cola está vacı́a; si lo está,
se crea un nuevo nodo que contiene a elemento, y se hace que tanto
inicio como fin, hagan referencia a dicho nodo (objeto). Si la cola no
está vacı́a (lı́nea 23), se crea un nuevo nodo y se agrega al final de la
cola (lı́nea 25); adicionalmente, se establece que el último elemento es
referido por fin (lı́nea 26).
2. El método elimina verifica (lı́nea 32) si la cola está vacı́a, si lo está, crea
y lanza la excepción ExcepcionEDVacia (lı́nea 33); en caso contrario,
recupera el dato almacenado (lı́nea 35), y actualiza las referencias co-
rrespondientes para inicio y fin (lı́neas 38-41), para finalmente, regresar
el dato recuperado referido por elemento (lı́nea 43).
La clase de prueba para la cola de espera del Ejemplo 5.3, se muestra en
el Ejemplo 5.4. La lı́nea 6 define la clase (Integer ) de los elementos a insertar
en la cola de espera, mientras que las lı́neas 9-12, realizan la inserción de los
números del cero al nueve. Note que por cada inserción, se imprime todo el
contenido de la cola.
Por otro lado, las lı́neas 15-24 realizan la eliminación de los elementos de
la cola. Dicho fragmento de código intenta eliminar once elementos (lı́neas
17-18)3 , y dado que el método elimina puede lanzar una excepción, el código
involucrado en la eliminación, como ya se mencionó en el Capı́tulo 4, de-
be estar dentro de una cláusula try-catch-finally, la cual permite atrapar
(cachar) las excepciones que un método pudiera lanzar.
3
Recuerde que sólo fueron insertados diez elementos, por lo que al intentar eliminar el
décimo primero, se generará la excepción ExcepcionEDVacia.
86 CAPÍTULO 5. COLAS DE ESPERA

1 /∗ C l a s e de p r u e b a de l a c l a s e Cola<T>.
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s PruebaCola {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 Cola<I n t e g e r > c o l a = new Cola<I n t e g e r >() ;
7
8 // Se i n s e r t a n d i e z e n t e r o s
9 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
10 cola . inserta ( i ) ;
11 c o l a . imprime ( ) ;
12 }
13 System . out . p r i n t l n ( ) ;
14
15 try {
16 // Se i n t e n t a e l i m i n a r once e n t e r o s
17 f o r ( i n t i = 0 ; i < 1 1 ; i ++){
18 I n t e g e r elemento = c o l a . elimina ( ) ;
19 System . out . p r i n t l n ( ” Elemento e l i m i n a d o de l a c o l a : ” +
elemento ) ;
20 c o l a . imprime ( ) ;
21 }
22 } catch ( ExcepcionEDVacia e ) {
23 e . printStackTrace () ;
24 }
25 }
26 }

Ejemplo 5.4: Clase de prueba para la cola de espera


La salida del Ejemplo 5.4, se muestra en la Figura 5.4. Asegúrese de
comprender, antes de continuar, por qué se generan cada uno de los elementos
(renglones) que componen dicha salida.

5.3. Colas de prioridad


La cola de prioridad es una estructura de datos en la que el ordena-
miento intrı́nseco de los elementos, determina el resultado de la aplicación
de sus operaciones básicas o primitivas.
En éste sentido, existen dos tipos de colas de prioridad:
1. Cola de prioridad ascendente.
2. Cola de prioridad descendente.
Ambas estructuras de datos redefinen la forma de operación convencio-
nal respecto de una cola de espera, por lo que serán estudiadas de manera
separada.
5.3. COLAS DE PRIORIDAD 87

Figura 5.4: Salida del Ejemplo 5.4


88 CAPÍTULO 5. COLAS DE ESPERA

Figura 5.5: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método elimina

5.3.1. Cola de prioridad ascendente


La cola de prioridad ascendente en un tipo de estructura de datos, en el
que la inserción de los elementos se realiza de manera convencional, pero la
eliminación se realiza en base al menor de los elementos almacenados en ella.
Para que ésto sea posible, los elementos que almacena la estructura de datos
deben tener una relación de orden, es decir, deben poseer algún mecanismo
que permita compararlos entre sı́.
La Figura 5.5 muestra la relación de clases en UML para una cola de
prioridad ascendente con la redefinición del método elimina. Observe cómo
se ha instrumentado la redefinición de dicho método por medio del mecanis-
mo de la herencia, y que las relaciones previamente existentes se conservan
(compare con la Figura 5.3).
Otro tipo de representación para la cola de prioridad ascendente, con-
siste en mantener ordenados los elementos de manera ascendente durante la
inserción, y conservar la operación de eliminación de la forma convencional.
Dicha representación se expresa en UML como en la Figura 5.6.
En resumen, en una cola de prioridad ascendente, los elementos se recu-
peran (eliminan) en orden ascendente respecto de la relación de orden que
guarden entre sı́. Ahora bien, para objetos con una relación de orden inheren-
te a ellos, como un objeto de la clase Integer o de la clase Float por ejemplo,
la comparación es posible, pero ¿Qué ocurre con objetos de la clase String del
API de Java, o con las clases Persona y Cientifico definidas en el Capı́tulo
5.3. COLAS DE PRIORIDAD 89

Figura 5.6: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método inserta

2, por mencionar sólo algunas?

Implementación
En la orientación a objetos, existe un concepto relacionado con la heren-
cia la herencia múltiple, que básicamente es la capacidad de una clase de
heredar los atributos y métodos de más de una clase padre; sin embargo,
el lenguaje de programación Java no incluye en su gramática dicha capaci-
dad, aunque por otro lado, incorpora un mecanismo que permite que una
clase se comprometa, a través de una especie de contrato, a implementar en
métodos, las operaciones definidas por medio de una interfaz (interface). La
interfaz Comparable del API de Java, obliga a las clases que la implementan,
a establecer una relación de orden entre los objetos que se deriven de ella.
Dicha relación de orden es arbitraria y está en función únicamente de las
necesidades especı́ficas de la clase en cuestión.
Para ilustrar lo anterior, considere el Ejemplo 5.5, el cual implementa una
cola de prioridad ascendente sobre escribiendo el método inserta y haciendo
uso de la interfaz Comparable, tal y como se propone en el diagrama de
clases UML de la Figura 5.6. Observe con detenimiento la lı́nea 5, la cual, en
el contexto de lo anterior, podrı́a interpretarse de la siguiente manera:

La clase ColaAscendente es una clase derivada o hija de la clase


Cola, y gestiona objetos genéricos T que definan una relación
90 CAPÍTULO 5. COLAS DE ESPERA

de orden, a través de la implementación del método compareTo


definido en la interfaz Comparable.

1 /∗ C l a s e que implementa una c o l a de p r i o r i d a d a s c e d e n t e de o b j e t o s


2 g e n e r i c o s . Se r e a l i z a l a s o b r e e s c r i t u r a d e l metodo i n s e r t a .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s ColaAscendente<T extends Comparable <T>> extends Cola<T>{
6 public C o l a A s c e n d e n t e ( ) {
7 t h i s ( ” Cola de P r i o r i d a d Ascendente ” ) ;
8 }
9
10 public C o l a A s c e n d e n t e ( S t r i n g s ) {
11 super ( s ) ;
12 }
13
14 public void i n s e r t a (T e l e m e n t o ) {
15 NodoG<T> n o d o A n t e r i o r , nodoActual , nodoNuevo = new NodoG<T>( e l e m e n t o ) ;
16
17 nodoAnterior = null ;
18 nodoActual = i n i c i o ;
19
20 while ( nodoActual != n u l l &&
21 ( e l e m e n t o . compareTo ( nodoActual . obtenDato ( ) ) ) > 0 ) {
22 n o d o A n t e r i o r = nodoActual ;
23 nodoActual = nodoActual . o b t e n S i g u i e n t e ( ) ;
24 }
25
26 i f ( n o d o A n t e r i o r == n u l l ) { // Se i n s e r t a a l p r i n c i p i o de l a c o l a
27 nodoNuevo . e s t a b l e c e S i g u i e n t e ( i n i c i o ) ;
28 i n i c i o = nodoNuevo ;
29 } else { // Se i n s e r t a en medio o a l f i n a l de l a c o l a
30 n o d o A n t e r i o r . e s t a b l e c e S i g u i e n t e ( nodoNuevo ) ;
31 nodoNuevo . e s t a b l e c e S i g u i e n t e ( nodoActual ) ;
32 i f ( nodoActual == n u l l ) // s e i n s e r t o a l f i n a l
33 f i n = nodoNuevo ;
34 }
35 }
36 }

Ejemplo 5.5: Clase que define una cola de prioridad ascendente sobre
escribiendo el método inserta
Observe que el mensaje o la invocación del método compareTo ocurre en
la lı́nea 21 del Ejemplo 5.5, y que la idea general del método inserta (lı́neas
14-35) consiste en recorrer la secuencia de nodos (lı́neas 20-24), mientras
haya nodos por procesar (lı́nea 20), y no se haya encontrado el lugar corres-
pondiente para el elemento a insertar (lı́nea 21). En éste sentido, el método
compareTo compara el objeto que recibe el mensaje con el objeto recibido
como argumento, y regresa uno de tres posibles valores4 :
4
Consulte en el API de Java la interfaz Comparable para ampliar y complementar la
5.3. COLAS DE PRIORIDAD 91

1. Un entero negativo (< 0) si el objeto que recibe el mensaje, es menor


que el objeto que recibe como argumento.

2. Cero (0) si el objeto que recibe el mensaje, es igual al objeto que recibe
como argumento.

3. Un entero positivo (> 0) si el objeto que recibe el mensaje, es mayor


que el objeto que recibe como argumento.

Es importante que el lector comprenda que el método compareTo es defi-


nido en la clase que quiera establecer una relación de orden para sus objetos,
es decir, los objetos de la clase genérica T, deberán tener la definición (código)
de dicho método.
Continuando con la explicación del método inserta del Ejemplo 5.5, note
que el método ha definido objetos auxiliares (lı́neas 17 y 18), para poder rea-
lizar el ajuste de las referencias correspondientes en las lı́neas 26-33. Aquı́ ca-
be mencionar, que aunque dichos objetos pudieron haber sido definidos como
atributos de la clase ColaAscendente, en realidad no representan una carac-
terı́stica o cualidad inherente a los objetos que se deriven de dicha clase, sino
que más bien son entidades útiles para la manipulación de la estructura de
datos dentro del método, por lo que de ser atributos, aunque la implemen-
tación trabajarı́a de la misma manera, el enfoque serı́a inapropiado, esto es,
serı́a un mal diseño. El análisis y los detalles del ajuste de las referencias
de las lı́neas 22-23 y 26-33, se dejan como ejercicio para el lector, y lo ins-
to amablemente a comprender completamente su funcionamiento, antes de
continuar.
Por último, la clase de prueba para la cola de prioridad ascendente del
Ejemplo 5.5 se muestra en el Ejemplo 5.6.
Tómese el lector el tiempo que considere necesario para comparar el Ejem-
plo 5.6 con el Ejemplo 5.4, y advierta que son, esencialmente iguales.
Note que los objetos a almacenar en la cola de prioridad ascendente son
objetos de la clase Integer (lı́nea 6), por lo que, para que no haya ningún
problema en la compilación, dicha clase deberá tener la implementación
(implements) de la interfaz Comparable, y en consecuencia, la definición
del método compareTo 5 .
información al respecto.
5
Se invita al lector para que realice dicha comprobación en el API de Java, antes de
compilar y ejecutar el Ejemplo 5.6.
92 CAPÍTULO 5. COLAS DE ESPERA

En base a lo anterior, todos los objetos que se deseen almacenar en la co-


la de prioridad ascendente definida en el Ejemplo 5.5, deberán implementar
dicha interfaz, y definir el comportamiento requerido por el método compa-
reTo 6 .

1 /∗ C l a s e de p r u e b a de l a c l a s e ColaAscendente<T>.
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s PruebaColaAscendente {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 ColaAscendente<I n t e g e r > c o l a A s c e n d e n t e = new
ColaAscendente<I n t e g e r >() ;
7
8 colaAscendente . inserta (1) ; c o l a A s c e n d e n t e . imprime ( ) ;
9 colaAscendente . inserta (8) ; c o l a A s c e n d e n t e . imprime ( ) ;
10 colaAscendente . inserta (3) ; c o l a A s c e n d e n t e . imprime ( ) ;
11 colaAscendente . inserta (6) ; c o l a A s c e n d e n t e . imprime ( ) ;
12 colaAscendente . inserta (5) ; c o l a A s c e n d e n t e . imprime ( ) ;
13 colaAscendente . inserta (4) ; c o l a A s c e n d e n t e . imprime ( ) ;
14 colaAscendente . inserta (7) ; c o l a A s c e n d e n t e . imprime ( ) ;
15 colaAscendente . inserta (2) ; c o l a A s c e n d e n t e . imprime ( ) ;
16 colaAscendente . inserta (9) ; c o l a A s c e n d e n t e . imprime ( ) ;
17
18 try {
19 f o r ( i n t i = 0 ; i < 9 ; i ++){
20 I n t e g e r elemento = colaAscendente . elimina ( ) ;
21 System . out . p r i n t l n ( ” Elemento e l i m i n a d o de l a c o l a a s c e n d e n t e : ”
+ elemento ) ;
22 c o l a A s c e n d e n t e . imprime ( ) ;
23 }
24 } catch ( ExcepcionEDVacia e ) {
25 e . printStackTrace () ;
26 }
27 }
28 }

Ejemplo 5.6: Clase de prueba para la cola de prioridad ascendente

Por último, observe que a diferencia del Ejemplo 5.4, el Ejemplo 5.6 in-
serta intencionalmente nueve números de manera desordenada, ya que la
implementación de cola de prioridad propuesta (Ejemplo 5.5) los mantie-
ne ordenados dentro de la estructura de datos, mientras que la eliminación
(lı́neas 18-26) se realiza de manera convencional. La salida del Ejemplo 5.6
se muestra en la Figura 5.7.

6
No olvide ésto el lector, ya que será de suma importancia para la realización de algunos
de los ejercicios del capı́tulo.
5.3. COLAS DE PRIORIDAD 93

Figura 5.7: Salida del Ejemplo 5.6


94 CAPÍTULO 5. COLAS DE ESPERA

Figura 5.8: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método elimina

5.3.2. Cola de prioridad descendente


La cola de prioridad descendente, es análoga en lo general a la cola de
prioridad ascendente.
La cola de prioridad descendente es un tipo de estructura de datos en
el que la inserción de los elementos se realiza también de la manera con-
vencional, pero la eliminación se realiza en base al mayor de los elementos
almacenados en ella. Al igual que para su contra parte, para que ésto último
sea posible, es necesario que los elementos que almacena la estructura de
datos tengan una relación de orden, es decir, es preciso que incorporen algún
mecanismo que les permita compararlos entre sı́.
La Figura 5.8 muestra la relación de clases en UML para una cola de
prioridad descendente con la redefinición del método elimina. Una vez más,
note cómo se ha instrumentado la redefinición de dicho método por medio
del mecanismo de la herencia, y que las relaciones (compare con la Figura
5.3) previamente existentes se conservan.
Al igual que para la cola de prioridad ascendente, otro tipo de represen-
tación para la cola de prioridad descendente consiste en mantener ordenados
los elementos de manera descendente durante la inserción, y conservar la
operación de eliminación de la forma convencional, tal y como se muestra en
la representación del diagrama de clases UML de la Figura 5.9.
Por último, recuerde que en una cola de prioridad descendente los ele-
mentos se recuperan (eliminan) en orden descendente, respecto de la relación
5.4. CONSIDERACIONES FINALES 95

Figura 5.9: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método inserta

de orden que guardan entre sı́. Los detalles de la implementación, se dejan


como ejercicio para el lector.

5.4. Consideraciones finales


Las colas de espera, y las colas de prioridad ascendentes y descendentes,
tienen amplias y muy variadas aplicaciones, que van desde la simulación y
modelado de situaciones relacionadas con las lı́neas de espera que hacemos
todos los dı́as (bancos, boletos, casetas de cobro, etc.), hasta aspectos de
bajo nivel relacionado con el funcionamiento de los sistemas operativos por
ejemplo.
Las colas de prioridad son un ejemplo sumamente claro en el que la defini-
ción de la estructura de datos o ADT, es independiente de la implementación.
En este capı́tulo, se propusieron y presentaron dos posibles implementaciones
para cada uno de los dos tipos de colas de prioridad, y se realizó la imple-
mentación completa de una de ellas, las implementaciones restantes se dejan
como ejercicio para el lector.
Finalmente, la introducción hacia las relaciones de orden en los objetos
por medio de la interfaz Comparable, resultará fundamental para los capı́tu-
los siguientes, por lo que se invita al lector a estudiar detenidamente estos
conceptos, y complementarlos con información adicional como ejercicio y la-
bor de investigación. Ası́ mismo, resulta sumamente importante que el lector
96 CAPÍTULO 5. COLAS DE ESPERA

realice la selección de ejercicios propuestos al final del capı́tulo, los cuales


tienen, como todos los ejercicios del libro, la finalidad de reforzar, ampliar, y
poner en práctica sus conocimientos. Le auguro éxito.
5.5. EJERCICIOS 97

5.5. Ejercicios
1. En el Ejemplo 5.3, el método inserta tiene la lı́nea 24 como comentario
¿Qué sucede si sustituye la lı́nea 25 por la lı́nea 24? ¿Compilará? Si
sı́ ¿por qué?, y si no ¿Por qué? Si compila ¿Cuál será el resultado de la
ejecución?
Determine sus respuestas, y después corrobore las mismas con la expe-
rimentación.

2. En el Ejemplo 5.4 se creó una cola de espera utilizando un constructor


sin argumentos. Modifique dicho ejemplo para asignar un nuevo nombre
a la cola por medio del constructor correspondiente, ası́gnele el nombre
de “Mi primera cola de espera”, recompile, y pruebe su funcionamiento.

3. Modifique el Ejemplo 5.3 para que:

a) Agregue el método peek a la implementación. Dicha operación


funciona de la siguiente manera: hecha un vistazo al elemento que
se encuentra en el inicio de la cola de espera y lo regresa, pero no
lo elimina.
b) Incorpore un atributo privado numérico (n), que lleve el control del
número de elementos insertados en la cola de espera. Al respecto
no olvide:
1) Inicializar explı́citamente dicho atributo a cero en el construc-
tor.
2) Proporcionar únicamente el método de tipo get para el atri-
buto n: obtenN().

4. Tomando como referencia el Ejemplo 5.4, modifı́quelo para que la cola


de espera del Ejemplo 5.3 almacene otro tipo de objetos además de los
de la clase Integer. Pruebe con al menos las siguientes clases:

Float.
String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).
98 CAPÍTULO 5. COLAS DE ESPERA

(a) Estado inicial

(b) Estado siguiente

Figura 5.10: Abstracción y representación de Round robin

5. Utilizando una cola de espera como la del Ejemplo 5.3, implemente el


algoritmo de Round robin.
Round robin es un método para seleccionar todos los elementos en un
grupo de manera equitativa y en orden, se comienza con el primer
elemento de la cola de espera y se procesa, y se continua de manera
progresiva hasta llegar al último elemento de la cola, para empezar
nuevamente desde el primer elemento. El principio general subyacente
detrás del método, consiste en que cada persona toma una parte de un
elemento compartido en cantidades iguales.
Considere la Figura 5.10 (a), la cual representa el estado inicial de una
cola de espera de números enteros que representan las cantidades a
considerar (quantum). El estado siguiente (Figura 5.10 (b)) consiste en
atender (restarle uno al quantum) al nodo que se encuentra al inicio de
la cola, y volverlo a formar al final de la misma para atender de manera
análoga a los siguientes nodos. Tome en consideración que, una vez que
el número llega a cero, el nodo correspondientes es eliminado.
El proceso anteriormente descrito continúa hasta atender o despachar
a todos los nodos formados en la cola. Para ello:

a) Genere un número aleatorio entre 10 y 50, el cual representará el


número de nodos que contendrá la cola de espera.
5.5. EJERCICIOS 99

b) Por cada nodo, genere nuevamente un número aleatorio entre 1 y


500, mismo que representará el quantum asignado a cada nodo.

No olvide construir también una clase de prueba para su implementa-


ción. La salida de su programa puede ser en la salida estándar o en un
archivo de texto. Es importante que compruebe el adecuado funciona-
miento de su implementación, ya que se reutilizará en otros ejercicios
del libro.

6. Modifique el Ejemplo 5.6 para que la cola de prioridad ascendente del


Ejemplo 5.5, almacene otro tipo de objetos además de los de la clase
Integer. Pruebe con al menos las siguientes clases:

Float.
String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).

Tome en cuenta que las clases Float y String del API de Java imple-
mentan la interfaz Comparable, pero que las clases Persona y Cientifico
no, por lo que como primer paso, deberá hacer que dichas clases imple-
menten la interfaz Comparable, y definan el método compareTo. Para
ello:

a) Realice el ordenamiento en base al atributo que representa la edad


de la persona para las instancias de la clase Persona.
b) Realice el ordenamiento en base al atributo que representa la es-
pecialidad del cientı́fico para las instancias de la clase Cientifico.

Como guı́a adicional para el lector, la clase PersonaComparable del


Ejemplo 5.7 realiza la implementación de la interfaz Comparable.
Observe cómo dicho ejemplo es esencialmente igual al Ejemplo 2.11
del Capı́tulo 2. Compare ambos ejemplos, estúdielos, ponga especial
atención en las lı́neas 5 y 57-63 del Ejemplo 5.7, y termine de resolver
lo que se planteó en este ejercicio.
1 /∗ La c l a s e PersonaComparable implementa l a i n t e r f a z Comparable
2 y d e f i n e e l metodo compareTo .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
100 CAPÍTULO 5. COLAS DE ESPERA

5 public c l a s s PersonaComparable implements


Comparable<PersonaComparable >{
6 private S t r i n g nombre ;
7 private i n t edad ;
8 private S t r i n g n a c i o n a l i d a d ;
9
10 PersonaComparable ( S t r i n g n ) {
11 nombre = n ;
12 }
13
14 PersonaComparable ( S t r i n g n , i n t e ) {
15 nombre = n ;
16 edad = e ;
17 }
18
19 PersonaComparable ( S t r i n g n , i n t e , S t r i n g nac ) {
20 nombre = n ;
21 edad = e ;
22 n a c i o n a l i d a d = nac ;
23 }
24
25 public void e s t a b l e c e N o m b r e ( S t r i n g n ) {
26 nombre = n ;
27 }
28
29 public S t r i n g obtenNombre ( ) {
30 return nombre ;
31 }
32
33 public void e s t a b l e c e E d a d ( i n t e ) {
34 edad = e ;
35 }
36
37 public i n t obtenEdad ( ) {
38 return edad ;
39 }
40
41 public void e s t a b l e c e N a c i o n a l i d a d ( S t r i n g n ) {
42 nacionalidad = n ;
43 }
44
45 public S t r i n g o b t e n N a c i o n a l i d a d ( ) {
46 return n a c i o n a l i d a d ;
47 }
48
49 public void mensaje ( ) {
50 System . out . p r i n t l n ( ” Puedo h a b l a r , mi nombre e s ” +
obtenNombre ( ) ) ;
51 }
52
53 public void comer ( ) {
54 System . out . p r i n t l n ( ”Mmmmmm uno de l o s p l a c e r e s de l a v i d a . . . ” ) ;
55 }
56
57 public i n t compareTo ( PersonaComparable p ) {
58 i f ( edad < p . obtenEdad ( ) )
59 return −1;
5.5. EJERCICIOS 101

60 e l s e i f ( edad > p . obtenEdad ( ) )


61 return 1 ;
62 return 0 ;
63 }
64 }

Ejemplo 5.7: Clase que implementa la interfaz Comparable

7. En base a las consideraciones hechas en el texto respecto de la cola de


prioridad ascendente, y al diseño propuesto por el diagrama de clases
UML de la Figura 5.5, realice la implementación de la cola de prioridad
ascendente correspondiente, y pruébela con las clases y las considera-
ciones hechas en el Ejercicio 6.

8. Realice la implementación de una cola de prioridad descendente de


manera análoga a la de su contraparte del Ejemplo 5.5. Para lo anterior,
tome en cuenta las consideraciones hechas en el texto, y lo expuesto en
el diseño representado por el diagrama de clases UML de la Figura 5.9.
No olvide definir también la clase de prueba correspondiente para su
propuesta. Puede basarse en la del Ejemplo 5.6.
Para sus pruebas, tome en cuenta al menos, las clases y consideraciones
propuestas en el Ejercicio 6.

9. En base a las consideraciones hechas en el texto respecto de la cola


de prioridad descendente, y al diseño propuesto por el diagrama de
clases UML de la Figura 5.8, realice la implementación de la cola de
prioridad descendente correspondiente, y pruébela con las clases y las
consideraciones hechas en el Ejercicio 6.

10. Considere la abstracción de la estructura de datos compuesta mostrada


en la Figura 5.11.
La estructura de datos de la Figura 5.11, está compuesta por una cola
de espera y varias pilas, una por cada nodo formado en la cola. Note
que cada nodo de la cola de espera, conserva una referencia a objetos
como él, y una referencia a objetos de tipo pila.
Este ejercicio consiste en hacer una representación de una fila de su-
permercado, en donde cada nodo formado en la cola de espera, simula
un carrito de supermercado con diferentes productos almacenados en
102 CAPÍTULO 5. COLAS DE ESPERA

Figura 5.11: Abstracción de una estructura de datos compuesta

él en forma de pila7 . Cada nodo de la pila representa un producto, por


lo que se requiere que la información que almacena la pila sean cade-
nas que representan la descripción del producto: leche, jamón, huevos,
cacahuates, etc.
Escriba un programa que modele e implemente la estructura de datos
planteada por el diagrama de la Figura 5.11. Serı́a sumamente con-
veniente y recomendable para el lector que, como parte de su diseño,
realizara el diagrama de clases UML de su propuesta de solución.

7
En la vida real no necesariamente es ası́, pero para el caso del ejercicio propuesto,
considérela de esa manera.
Capı́tulo 6

Listas enlazadas

I’m very much into making lists and breaking things apart into
categories.
David Byrne

Lists have always implied social order.


David Viscott

6.1. Definición
Una lista es, en general, una colección lineal de elementos, mientras que
una lista enlazada es, en el contexto que nos compete, una colección lineal
de objetos (nodos) auto referidos.
Se tiene acceso a una lista enlazada por medio de una referencia al primer
nodo de la lista. Aunque resulta más conveniente, por las caracterı́sticas
inherentes a la estructura de datos, que existan dos referencias a la misma:
una que refiera el inicio de la lista, y otra que refiera el fin de la lista. El
acceso a los nodos intermedios subsecuentes, se realiza a través del enlace o
referencia que contiene cada uno de ellos.
Una lista enlazada es más conveniente que un arreglo estático por ejemplo,
cuando no es posible determinar con anticipación el número de elementos a
almacenar en la estructura de datos.
Las listas enlazadas son dinámicas, por lo que se puede aumentar o dismi-
nuir a discreción el número de elementos de una lista. Un aspecto importante
a considerar respecto a las listas enlazadas, es que pueden, por conveniencia,

103
104 CAPÍTULO 6. LISTAS ENLAZADAS

mantenerse en orden1 insertando cada nuevo elemento en el punto apropiado


dentro de la lista enlazada.
Normalmente, los nodos de las listas enlazadas no están almacenados en
forma contigua en la memoria; sin embargo, lógicamente los nodos aparecen
como contiguos. Ésto obedece a su representación fı́sica y lógica respectiva-
mente.

6.1.1. Operaciones primitivas


Se definen cinco operaciones primitivas sobre una lista enlazada:

1. La operación insertaAlInicio, agrega un elemento al inicio de la lista


enlazada.

2. La operación insertaAlFinal, agrega un elemento al final de la lista


enlazada.

3. La operación eliminaDelInicio elimina un elemento del inicio de la


lista enlazada.

4. La operación eliminaDelFinal elimina un elemento del final de la lista


enlazada.

5. La operación estaVacia regresa verdadero o falso, dependiendo de si


la lista enlazada contiene o no elementos respectivamente.

Tome en cuenta que los elementos de una lista enlazada podrı́an ser inser-
tados en cualquier parte, y que en función de ellos, podrı́an ser definidas más
operaciones sobre una lista enlazada, por lo que el mecanismo de inserción es-
tará en función directa de las necesidades especı́ficas para la implementación
de la estructura de datos.
En éste sentido, si se desea mantener una lista enlazada ordenada por
ejemplo, se deberá ir recorriendo la lista enlazada de manera secuencial, hasta
encontrar el lugar apropiado para la inserción de cada uno de los elementos
que la conforman.
Por otro lado, la eliminación de un elemento particular, podrı́a consistir en
primer lugar, en la localización de dicho elemento dentro de la lista enlazada.
1
Puede resultar sumamente conveniente, pero en definitiva, no es una caracterı́stica
inherente a la estructura de datos.
6.1. DEFINICIÓN 105

Figura 6.1: Abstracción de una lista enlazada como una secuencia de nodos

Si existe, se procede a su eliminación; en caso contrario, se deberı́a indicar que


el elemento que se está intentando eliminar, no existe dentro de la estructura
de datos.

6.1.2. Representación
Como se mencionó en el apartado anterior, el acceso a una lista enlazada
se realiza por al menos una referencia al primer nodo de dicha lista enlazada.
Aunque por otro lado, resulta más conveniente, por las caracterı́sticas inhe-
rentes a la estructura de datos, que existan dos referencias hacia la misma:
una que refiera el inicio de la lista enlazada, y otra que refiera el fin de la
lista enlazada.
El acceso a los nodos intermedios subsecuentes, se realiza a través del
enlace o referencia que contiene cada uno de ellos. Por regla convencional,
para marcar el fin de la lista enlazada, el enlace al siguiente nodo, en el último
nodo de la lista enlazada, se establece a null.
La representación mostrada en la Figura 6.1, es una abstracción de la
implementación que se realizará en la siguiente sección, y coincide con la
representación lógica de una lista enlazada.
Observe cómo la Figura 6.1 luce muy similar a la Figura 5.2 del Capı́tulo
5, ya que hace uso también de dos referencias: inicio y fin, las cuales denotan
respectivamente, el primero y el último de los elementos almacenados en la
lista enlazada. Note también que, para el caso de un solo elemento, dichas
referencias harán referencia, valga la redundancia, al mismo nodo.
Por otro lado, el diagrama de clases UML presentado en la Figura 6.2,
muestra el diseño de clases de la lista enlazada que se desea implementar.
Dicho diseño complementa, en conjunción con la definición hecha con ante-
rioridad, el concepto de lista enlazada.
A su vez, el diagrama de clases UML de la Figura 6.2, muestra también
la relación que existe entre las clases más importantes que se involucrarán
106 CAPÍTULO 6. LISTAS ENLAZADAS

Figura 6.2: Diagrama de clases UML para la lista enlazada

durante la implementación de la lista enlazada. Tome el lector el tiempo que


considere necesario para revisar, analizar y comprender el diagrama de clases
de la Figura 6.2, antes de avanzar a la siguiente sección.

6.2. Implementación
La implementación de una lista enlazada se muestra en el Ejemplo 6.1.
Note que en base a lo descrito en el diagrama de clases UML de la Figura 6.2,
y a lo definido en el código fuente de dicho ejemplo, se hace uso de las clases
NodoG y ExcepcionEDVacia, mismas que han sido explicadas y analizadas
en capı́tulos anteriores, por lo que ya no se presentan ni se discuten aquı́2 .
Adicionalmente a lo anterior, el Ejemplo 6.1 muestra la definición de
los métodos estaVacia e imprime, los cuales también han sido presentados
en ejemplos anteriores; de hecho, se han reutilizado con toda la intención, ya
que el comportamiento representado por ellos, cumple con los requerimientos
necesarios para una lista enlazada. En base a lo anterior, únicamente se
describirán los siguientes métodos:

insertaAlInicio (lı́neas 18-23), como su nombre lo indica, el método inserta


elementos en la parte referida por inicio.
Si la estructura de datos está vacı́a (lı́nea 19), se crea el nodo (objeto)
con el elemento correspondiente, el cual será referido tanto por ini-
cio como por fin (lı́nea 20). En caso contrario, se inserta el elemento
siguiendo la misma idea que se utilizó para la pila (lı́nea 22).
2
Si el lector quiere mayor referencia de dichas clases, refiérase a los Capı́tulos 4 y 5.
6.2. IMPLEMENTACIÓN 107

insertaAlFinal (lı́neas 25-32), como su nombre lo indica, el método inserta


elementos en la parte referida por fin.
Si la estructura de datos está vacı́a (lı́nea 26), se crea el nodo (objeto)
con el elemento correspondiente, el cual será referido tanto por ini-
cio como por fin (lı́nea 27)3 . En caso contrario, se inserta el elemento
siguiendo la misma idea que se utilizó para la cola de espera (lı́neas
29-30).
1 /∗ C l a s e que implementa una l i s t a e n l a z a d a de o b j e t o s g e n e r i c o s de l a
c l a s e NodoG<T>.
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s L i s t a <T>{
5 private NodoG<T> i n i c i o ;
6 private NodoG<T> f i n ;
7 private S t r i n g nombre ;
8
9 public L i s t a ( ) {
10 this ( ” Lista ” ) ;
11 }
12
13 public L i s t a ( S t r i n g n ) {
14 nombre = n ;
15 i n i c i o = f i n = null ;
16 }
17
18 public void i n s e r t a A l I n i c i o (T e l e m e n t o ) {
19 i f ( estaVacia () )
20 i n i c i o = f i n = new NodoG<T>( e l e m e n t o ) ;
21 else
22 i n i c i o = new NodoG<T>( elemento , i n i c i o ) ;
23 }
24
25 public void i n s e r t a A l F i n a l (T e l e m e n t o ) {
26 i f ( estaVacia () )
27 i n i c i o = f i n = new NodoG<T>( e l e m e n t o ) ;
28 else {
29 f i n . e s t a b l e c e S i g u i e n t e (new NodoG<T>( e l e m e n t o ) ) ;
30 fin = fin . obtenSiguiente () ;
31 }
32 }
33
34 public T e l i m i n a D e l I n i c i o ( ) throws ExcepcionEDVacia {
35 i f ( estaVacia () )
36 throw new ExcepcionEDVacia ( nombre ) ;
37
38 T e l e m e n t o = i n i c i o . obtenDato ( ) ;
39
40 // a c t u a l i z a r e f e r e n c i a s
41 i f ( i n i c i o == f i n )

3
Observe que hasta aquı́, se hace exactamente lo mismo que para el método anterior:
insertaAlInicio.
108 CAPÍTULO 6. LISTAS ENLAZADAS

42 i n i c i o = f i n = null ;
43 else
44 i n i c i o = i n i c i o . obtenSiguiente () ;
45
46 return e l e m e n t o ;
47 }
48
49 public T e l i m i n a D e l F i n a l ( ) throws ExcepcionEDVacia {
50 i f ( estaVacia () )
51 throw new ExcepcionEDVacia ( nombre ) ;
52
53 T e l e m e n t o = f i n . obtenDato ( ) ;
54
55 // a c t u a l i z a r e f e r e n c i a s
56 i f ( i n i c i o == f i n )
57 i n i c i o = f i n = null ;
58 e l s e { // d e t e r m i n a q u i e n s e r a e l nuevo f i n ( Por que ?)
59 NodoG<T> a c t u a l = i n i c i o ;
60 while ( a c t u a l . o b t e n S i g u i e n t e ( ) != f i n )
61 actual = actual . obtenSiguiente () ;
62
63 f i n = a c t u a l ; // nodo a c t u a l e s e l nuevo f i n
64 a c t u a l . e s t a b l e c e S i g u i e n t e ( null ) ;
65 }
66
67 return e l e m e n t o ;
68 }
69
70 public boolean e s t a V a c i a ( ) {
71 return i n i c i o == n u l l ;
72 }
73
74 public void imprime ( ) {
75 i f ( estaVacia () )
76 System . out . p r i n t l n ( ” Vacia : ” + nombre ) ;
77 else {
78 System . out . p r i n t ( ”La ” + nombre + ” e s : ” ) ;
79 NodoG<T> a c t u a l = i n i c i o ;
80
81 while ( a c t u a l != n u l l ) {
82 System . out . p r i n t ( a c t u a l . obtenDato ( ) + ” ” ) ;
83 actual = actual . obtenSiguiente () ;
84 }
85 System . out . p r i n t l n ( ) ;
86 }
87 }
88 }

Ejemplo 6.1: Definición de la clase Lista que permite almacenar objetos


genéricos

eliminaDelInicio (lı́neas 34-47), como su nombre lo indica, este método


elimina elementos en la parte referida por inicio.
Si la estructura de datos está vacı́a (lı́nea 35), se lanza la excepción
6.2. IMPLEMENTACIÓN 109

ExcepcionEDVacia (lı́nea 36). En caso contrario, se procede a la elimi-


nación del elemento de manera análoga a como se hizo para la cola de
espera (lı́neas 38-46).

eliminaDelFinal (lı́neas 49-68), como su nombre lo indica, elimina elemen-


tos en la parte referida por fin.
Si la estructura de datos está vacı́a (lı́nea 50), se lanza la excepción
ExcepcionEDVacia (lı́nea 51). En caso contrario, se procede a la elimi-
nación del elemento correspondiente, para ello:

1. Si sólo existe un elemento (lı́nea 56), se actualizan las referencias


(lı́nea 57). En caso contrario (lı́nea 58):
2. Se realiza un recorrido (lı́neas 59-61) por la estructura de datos
desde el inicio (lı́nea 59), para determinar el elemento anterior al
referido por fin (¿Por qué?).
3. Se actualizan las referencias correspondientes (lı́neas 63-64).

La clase de prueba para el Ejemplo 6.1 se muestra en el Ejemplo 6.2. Note


que se insertan diez números enteros, y que para los números pares se utiliza
el método insertaAlInicio, mientras que para los números impares se utiliza
el método insertaAlFinal. Observe también cómo para la eliminación ocurre,
de manera análoga, lo correspondiente.
1 /∗ C l a s e de p r u e b a para l a c l a s e L i s t a .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s P r u e b a L i s t a {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 L i s t a <I n t e g e r > l i s t a = new L i s t a <I n t e g e r >() ;
7
8 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
9 i f ( i % 2 == 0 )
10 lista . insertaAlInicio ( i ) ; // par
11 else
12 l i s t a . insertaAlFinal ( i ) ; // impar
13 l i s t a . imprime ( ) ;
14 }
15 System . out . p r i n t l n ( ) ;
16
17 try {
18 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
19 I n t e g e r elemento ;
20 i f ( i % 2 == 0 )
21 elemento = l i s t a . e l i m i n a D e l I n i c i o ( ) ; // impar
22 else
23 elemento = l i s t a . e l i m i n a D e l F i n a l ( ) ; // par
110 CAPÍTULO 6. LISTAS ENLAZADAS

24 System . out . p r i n t l n ( ” Elemento e l i m i n a d o de l a l i s t a : ” +


elemento ) ;
25 l i s t a . imprime ( ) ;
26 }
27 } catch ( ExcepcionEDVacia e ) {
28 e . printStackTrace () ;
29 }
30 }
31 }

Ejemplo 6.2: Clase de prueba para la clase Lista


Finalmente, la Figura 6.3 muestra la salida del Ejemplo 6.2.

6.3. Herencia vs. composición


Uno de los aspectos más importantes de la programación orientada a
objetos, es la conveniencia de la reutilización de código por medio de la
abstracción. En éste sentido, dos de los esquemas más comunes al respecto
son: la herencia y la composición.
Esta sección presenta por medio de un ejemplo ya conocido y presenta-
do previamente al lector, la implementación de una pila utilizando una lista
enlazada. Dicha implementación se realiza empleando los dos enfoques men-
cionados con anterioridad. Ası́ mismo, se analizan las ventajas y desventajas
de cada uno de ellos.

6.3.1. Implementación de una pila utilizando herencia


La implementación de una pila, por medio de una lista con un enfoque
basado en la herencia, es en realidad bastante simple, tal y como lo muestra
el código del Ejemplo 6.3.
Observe que la clase del Ejemplo 6.3 no define atributos, y que únicamente
define dos constructores y los métodos push y pop; lo cual también ha sido
representado en el diagrama de clases UML de la Figura 6.4.
Insto nueva y amablemente al lector a que compare, contraste, y analice
con detenimiento, antes de continuar, a la Figura 6.4 con el Ejemplo 6.3.
Note que los métodos push (lı́neas 14-16), y pop (lı́neas 18-20) del Ejemplo
6.3, no hacen otra cosa más que encapsular el comportamiento de los métodos
insertaAlInicio y eliminaDelInicio respectivamente, los cuales son servicios
definidos en la clase Lista del Ejemplo 6.1, y es posible accederlos, debido a
que la clase PilaH hereda de la clase Lista (lı́nea 5).
6.3. HERENCIA VS. COMPOSICIÓN 111

Figura 6.3: Salida del Ejemplo 5.4


112 CAPÍTULO 6. LISTAS ENLAZADAS

Figura 6.4: Diagrama de clases UML para la implementación de una pila


utilizando herencia y una lista lista enlazada

Observe que como parte del mecanismo de la herencia, tampoco es nece-


sario definir el comportamiento de los métodos estaVacia e imprime dentro
de la clase PilaH, ya que se encuentran definidos en la clase Lista.
1 /∗ C l a s e que implementa una p i l a de o b j e t o s nodo de l a c l a s e NodoG<T>,
2 u t i l i z a n d o h e r e n c i a y una l i s t a e n l a z a d a .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s PilaH<T> extends L i s t a <T>{
6 public PilaH ( ) {
7 this ( ” Pila generica ” ) ;
8 }
9
10 public PilaH ( S t r i n g n ) {
11 super ( n ) ;
12 }
13
14 public void push (T e l e m e n t o ) {
15 i n s e r t a A l I n i c i o ( elemento ) ;
16 }
17
18 public T pop ( ) throws ExcepcionEDVacia {
19 return e l i m i n a D e l I n i c i o ( ) ;
20 }
21 }

Ejemplo 6.3: Definición de la clase PilaH que implementa una pila de objetos
genéricos utilizando herencia y una lista enlazada
El primero de dichos comportamientos forma parte de la definición de las
6.3. HERENCIA VS. COMPOSICIÓN 113

primitivas de la estructura de datos pila, mientras que el segundo es utilizado


en la clase PruebaPilaH del Ejemplo 6.4 (lı́neas 11 y 20), la cual es la clase
de prueba del Ejemplo 6.3.
1 /∗ C l a s e de p r u e b a para PilaH .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s PruebaPilaH {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 PilaH<I n t e g e r > p i l a = new PilaH<I n t e g e r >() ;
7
8 // Se i n s e r t a n d i e z e n t e r o s
9 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
10 p i l a . push ( i ) ;
11 p i l a . imprime ( ) ;
12 }
13 System . out . p r i n t l n ( ) ;
14
15 try {
16 // Se i n t e n t a e l i m i n a r once e n t e r o s
17 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
18 I n t e g e r e l e m e n t o = p i l a . pop ( ) ;
19 System . out . p r i n t l n ( ” Elemento e l i m i n a d o de l a p i l a : ” +
elemento ) ;
20 p i l a . imprime ( ) ;
21 }
22 } catch ( ExcepcionEDVacia e ) {
23 e . printStackTrace () ;
24 }
25 }
26 }

Ejemplo 6.4: Clase de prueba para PilaH

La salida del Ejemplo 6.4 se muestra en la Figura 6.5.

Consideraciones
Al menos en apariencia, el mecanismo de la herencia resulta sumamente
conveniente en base a lo expuesto con anterioridad; sin embargo, presenta
algunos inconvenientes que vale la pena considerar.
Las instancias de la clase PilaH, al heredar las caracterı́sticas y el compor-
tamiento inherentes a una lista enlazada, pueden hacer uso de los métodos de
inserción y de eliminación correspondientes a una lista enlazada, por lo que
desde esta perspectiva, un objeto de dicha clase podrı́a permitir inserciones
y eliminaciones, no únicamente del tope de la pila (representado por inicio),
sino también de la base de la pila (“representada”por fin) a la que se supone,
por definición, no se debe tener acceso.
114 CAPÍTULO 6. LISTAS ENLAZADAS

Figura 6.5: Salida del Ejemplo 6.4


6.3. HERENCIA VS. COMPOSICIÓN 115

Figura 6.6: Diagrama de clases UML para la implementación de una pila


utilizando composición y una lista lista enlazada

Tome en consideración que todavı́a se podrı́a transgredir más la defini-


ción de una pila, ya que potencialmente es posible insertar en, o eliminar de
cualquier parte de la estructura de datos con las modificaciones correspon-
dientes, lo cual queda completamente fuera tanto de la definición que se hizo
de la pila, como de las operaciones primitivas que le fueron definidas.

6.3.2. Implementación de una pila utilizando compo-


sición
La implementación de una pila utilizando el enfoque de composición, se
basa en la idea de que una lista enlazada, contiene ya definidas las operaciones
que necesita una pila, pero que también, como ya se discutió con anterioridad,
contiene otras operaciones que no deberı́an ser utilizadas por la misma.
En función de lo anterior y de manera conveniente, lo que se hace es
encapsular las operaciones de la lista enlazada dentro de las de la pila, con
la finalidad de proporcionar una interfaz o un conjunto de servicios ad hoc
con la definición de la estructura de datos en cuestión: la pila.
La Figura 6.6 muestra el diseño en diagrama de clases UML de la de-
finición de una pila que utiliza una lista enlazada para su implementación.
116 CAPÍTULO 6. LISTAS ENLAZADAS

Observe y compare con detenimiento dicho diagrama con el de la Figura 6.4,


y note que el cambio principal está en la clase PilaC y en la relación entre
ésta y la clase Lista.
Por otro lado, la definición de la clase PilaC se muestra en el Ejemplo
6.5. Insto una vez más al lector a que ponga atención en dos aspectos:

1. La relación entre el diagrama de clases de la Figura 6.6 y el Ejemplo


6.5.

2. Los métodos push (lı́neas 16-18), pop (lı́neas 20-22) e imprime (lı́neas
24-26) no hacen otra cosa más que encapsular, de manera conveniente,
los mensajes enviados al objeto tope (lı́nea 6), mismos que son llevados
a cabo por los métodos correspondientes definidos en la clase Lista.

1 /∗ C l a s e que implementa una p i l a de o b j e t o s nodo de l a c l a s e NodoG<T>,


2 u t i l i z a n d o c o m p o s i c i o n y una l i s t a e n l a z a d a .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s PilaC<T>{
6 private L i s t a <T> t o p e ;
7
8 public PilaC ( ) {
9 this ( ” Pila generica ” ) ;
10 }
11
12 public PilaC ( S t r i n g n ) {
13 t o p e = new L i s t a <T>(n ) ;
14 }
15
16 public void push (T e l e m e n t o ) {
17 tope . i n s e r t a A l I n i c i o ( elemento ) ;
18 }
19
20 public T pop ( ) throws ExcepcionEDVacia {
21 return t o p e . e l i m i n a D e l I n i c i o ( ) ;
22 }
23
24 public void imprime ( ) {
25 t o p e . imprime ( ) ;
26 }
27 }

Ejemplo 6.5: Definición de la clase PilaC que implementa una pila de objetos
genéricos utilizando composición y una lista enlazada
En base a lo anterior, los objetos instanciados de la clase PilaC, sólo
podrán acceder a los servicios definidos explı́citamente en dicha clase y a
ningún otro más, lo cuál hace que, desde el punto de vista de la abstracción
6.4. LISTAS CIRCULARES 117

y la representación de la estructura de datos, el enfoque de la composición


sea mucho más conveniente que el de la herencia.
La clase de prueba del Ejemplo 6.5 se muestra en el Ejemplo 6.6, y sigue
el mismo mecanismo de inserción de los ejemplos de prueba anteriores.
1 /∗ C l a s e de p r u e b a para PilaC .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s PruebaPilaC {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 PilaC<I n t e g e r > p i l a = new PilaC<I n t e g e r >() ;
7
8 // Se i n s e r t a n d i e z e n t e r o s
9 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
10 p i l a . push ( i ) ;
11 p i l a . imprime ( ) ;
12 }
13 System . out . p r i n t l n ( ) ;
14
15 try {
16 // Se i n t e n t a e l i m i n a r once e n t e r o s
17 f o r ( i n t i = 0 ; i < 1 0 ; i ++){
18 I n t e g e r e l e m e n t o = p i l a . pop ( ) ;
19 System . out . p r i n t l n ( ” Elemento e l i m i n a d o de l a p i l a : ” +
elemento ) ;
20 p i l a . imprime ( ) ;
21 }
22 } catch ( ExcepcionEDVacia e ) {
23 e . printStackTrace () ;
24 }
25 }
26 }

Ejemplo 6.6: Clase de prueba para PilaC

Finalmente, compruebe que la salida del Ejemplo 6.6 coincide exacta-


mente con la mostrada en la Figura 6.5, la cual se presentó también como la
salida correspondiente para el Ejemplo 6.4.

6.4. Listas circulares


Las listas enlazadas, también denominadas listas simplemente enlazadas,
son sumamente útiles y convenientes para diferentes usos y aplicaciones, al-
gunas de las cuales se expusieron en la secciones anteriores; sin embargo,
como casi todo en la vida, presentan ciertos inconvenientes.
Algunos de los inconvenientes que se tienen con una lista simplemente
enlazada son:
118 CAPÍTULO 6. LISTAS ENLAZADAS

Figura 6.7: Abstracción de una lista enlazada circular como una secuencia de
nodos

Dada una única referencia a un nodo determinado de la lista enlazada,


y suponiendo que éste no sea el primero, no se puede llegar a ninguno
de los nodos que lo preceden.

Si por una determinada razón se recorre la lista enlazada, siempre debe


conservarse una referencia externa4 al inicio de dicha lista enlazada,
con la finalidad de poder volver a recorrer la lista nuevamente, en caso
de ser necesario.

En base a lo anterior, considere la siguiente modificación respecto a la


estructura de una lista simplemente enlazada:

El elemento siguiente en el último nodo, hará referencia al primer


nodo y no a null; a diferencia de lo que se define para una lista
simplemente enlazada convencional.

Al tipo de lista enlazada que adopta dicha modificación se le denomina


lista enlazada circular, y su representación se muestra en la Figura 6.7.
Es importante hacer notar que en una lista enlazada circular, es posible
llegar a cualquier otro nodo de la estructura de datos, partiendo de un no-
do distinto cualquiera, lo cual subsana los dos inconvenientes enunciados al
principio, para una lista simplemente enlazada.
Observe también que una lista enlazada circular no tiene un “primer” o
“último” nodo natural, por lo que se debe establecer un primer y último nodo
por convención5 .
Finalmente, tome en cuenta que una referencia a null, representa una lista
circular vacı́a. En la sección de ejercicios tendrá oportunidad de experimentar
con la implementación de dicha estructura de datos.
4
Además de la que se utilizó para el recorrido.
5
Lo cual es más una conveniencia, que una caracterı́stica inherente a la estructura de
datos.
6.5. LISTAS DOBLEMENTE ENLAZADAS 119

6.4.1. El problema de Josephus


El siguiente es problema clásico, y su solución utilizando listas enlazadas
circulares, es una aplicación clave:

Un grupo de soldados se encuentra rodeado por una abruma-


dora fuerza enemiga. No hay esperanza de victoria sin refuerzos.
Lamentablemente, sólo hay un caballo disponible para ir en su
busca (o escapar).
Los soldados realizan un pacto de sangre para determinar cuál
de ellos tomará el caballo y, en el mejor de los casos, pedirá ayu-
da. Para ello, forman un cı́rculo y van eligiendo un número de
un sombrero (n). También se elige uno de los nombres de los
soldados, de otro sombrero.
Iniciando con el soldado cuyo nombre se eligió, se empieza
a contar en el sentido de las manecillas del reloj alrededor del
cı́rculo. Cuando la cuenta llega a n, el soldado correspondiente
se retira del cı́rculo y la cuenta vuelve a empezar con el soldado
siguiente.
El proceso continúa de manera análoga hasta que sólo queda
un soldado, el cuál será el que va a tomar el caballo y pedirá ayuda
(o escapará trágica e irremediablemente).

En resumen el problema de Josephus es: dado un número n, el ordena-


miento de los soldados en el cı́rculo, y el soldado a partir del que comienza
la cuenta, determinar:

El orden en el cual se eliminan los soldados del cı́rculo.

Cuál soldado escapa.

La solución al problema de Josephus, se deja como ejercicio para el lector.

6.5. Listas doblemente enlazadas


Aunque una lista enlazada circular tiene ventajas sobre una lista simple-
mente enlazada6 , tiene también algunas desventajas:
6
Dichas ventajas se comentaron en la sección anterior.
120 CAPÍTULO 6. LISTAS ENLAZADAS

No es posible recorrer la lista enlazada circular hacia atrás (o hacia la


izquierda), es decir, sólo puede recorrerse en un sentido.

Dada una referencia a un nodo determinado de la lista enlazada cir-


cular, no es posible eliminar dicho nodo utilizando únicamente dicha
referencia (¿Por qué?).

Una estructura de datos conveniente para subsanar dichas desventajas,


es precisamente, la lista doblemente enlazada.

6.5.1. Definición, primitivas y representación


Una lista doblemente enlazada es una colección lineal de nodos auto
referidos, en donde cada nodo tiene dos referencias:

1. Una referencia al sucesor del nodo actual (siguiente).

2. Una referencia al antecesor del nodo actual (anterior ).

La representación de una lista doblemente enlazada se muestra en la Fi-


gura 6.8 (a). Observe cómo al igual que en una lista enlazada, una lista
doblemente enlazada puede ser también doblemente enlazada circular (Fi-
gura 6.8 (b)). Esta última, sigue también las mismas consideraciones que se
hicieron en su momento para una lista enlazada.
Adicionalmente, las operaciones primitivas para una lista doblemente en-
lazada son exactamente las mismas que para una lista enlazada.
El diagrama de clases UML para una lista doblemente enlazada se pre-
senta en la Figura 6.9, el cual complementa la definición a través del corres-
pondiente diseño de clases.
Por ultimo, note que dicho diagrama presentado en la Figura 6.9 es sus-
tancialmente distinto de los que en general se han utilizado para el diseño
de las anteriores estructuras de datos, y que con excepción de la clase Ex-
cepcionEDVacia, tanto la clase ListaDoble como la clase NodoDobleG, han
sufrido cambios significativos. Consulte el ejercicio 8 para una ampliación al
respecto.
6.5. LISTAS DOBLEMENTE ENLAZADAS 121

(a) Simple

(b) Circular

Figura 6.8: Abstracción de una lista doblemente enlazada

Figura 6.9: Diagrama de clases UML para una lista doblemente enlazada
122 CAPÍTULO 6. LISTAS ENLAZADAS

6.6. Consideraciones finales


Una de las aplicaciones más útiles de las colas, las colas de prioridad y
de las listas vinculadas es la simulación.
Un programa de simulación modela una situación del mundo real para
aprender algo de ella. Cada objeto y acción del mundo tiene su representación
en el programa.
Si la simulación es precisa, el resultado del programa reflejará el resultado
de las acciones que se simulan, por lo que será posible comprender lo que ocu-
rre en una situación del mundo real sin necesidad de observar su ocurrencia
en el mundo real.
Algunos de los contextos en los que es posible aplicar técnicas de simula-
ción utilizando las estructuras de datos anteriormente mencionadas son:

Bancos.

Lı́neas aéreas.

Casetas de cobro de autopistas.

Terminal de autobuses.

Un largo etcétera.

Las listas enlazadas completan el espectro de estructuras de datos lineales


que se inició con las pilas, y es de notarse que se ha ido avanzando de lo
particular, hacia lo general, ya que como pudo apreciarse, las listas enlazadas
son las estructuras de datos que menos restricciones tienen en su definición
y operaciones.
Tanto los usos como las aplicaciones de las pilas, las colas de espera y
las listas enlazadas son prácticamente infinitas, limitadas únicamente, por la
imaginación del programador.
6.7. EJERCICIOS 123

6.7. Ejercicios
1. Considerando la definición de una lista enlazada, modifique el Ejemplo
6.1 para que:

a) Incorpore un atributo privado numérico (n), que lleve el control del


número de elementos insertados en la lista enlazada. Al respecto
no olvide:
1) Inicializar explı́citamente dicho atributo a cero en el construc-
tor.
2) Proporcionar únicamente el método de tipo get para el atri-
buto n: obtenN().
b) Considere otras operaciones de inserción para la lista enlazada,
como por ejemplo:
insertaDespuesDe(e1, e2) inserta al elemento e1 después del
elemento e2.
Si e1 pudo ser insertado, se debe regresar un valor entero
distinto de cero para notificar éxito. En caso contrario7 , se
deberá regresar cero.
insertaAntesDe(e1, e2) inserta al elemento e1 antes del ele-
mento e2.
Si e1 pudo ser insertado, se debe regresar un valor entero
distinto de cero para notificar éxito. En caso contrario8 , se
deberá regresar cero.
insertaOrdenado(elemento) inserta al elemento en orden as-
cendente dentro de la lista.

De preferencia genere, como parte de su diseño, el diagrama de clases


UML para la nueva definición de la lista enlazada. Puede apoyarse de
los diagramas de clases de la Figura 6.2, y el de la Figura 6.9.

2. Modifique el Ejemplo 6.2, para que almacene otro tipo de objetos


además de los de la clase Integer. Pruebe con al menos las siguientes
clases:
7
Quizá el elemento e2 ni siquiera exista dentro de la lista enlazada.
8
Ídem.
124 CAPÍTULO 6. LISTAS ENLAZADAS

Float.
String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).

3. En el texto se hace mención de los inconvenientes de la implementación


de una pila por medio de la herencia de una lista enlazada. Compruebe
con al menos un ejemplo, la posibilidad de realizar una operación no
válida para una pila. Puede basarse en el Ejemplo 6.4.

4. En base a lo descrito en el texto, realice la implementación de una


cola de espera utilizando una lista enlazada. Para lo anterior, siga los
enfoques de:

a) Herencia (apóyese del Ejemplo 6.3).


b) Composición (apóyese del Ejemplo 6.5).

Determine sus propias conclusiones respecto al mecanismo de herencia


vs. composición para ambos tipos de implementación.

5. Modifique el Ejemplo 6.4 y el Ejemplo 6.6 para realizar las clases de


pruebas respectivas del Ejercicio 4.
Posteriormente, asegúrese de que es posible almacenar otro tipo de
objetos además de los de la clase Integer. Pruebe con al menos las
siguientes clases:

Float.
String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).

6. En base a la descripción hecha en el texto ¿Cuáles son las consideracio-


nes necesarias para la implementación de una lista enlazada circular?
¿Cómo deben hacerse las inserciones? ¿Cómo deben hacerse las elimina-
ciones? ¿Cuáles serı́an las operaciones primitivas respecto de una lista
simplemente enlazada? ¿Qué otras operaciones, además de las presen-
tadas en el texto se podrı́an definir?
6.7. EJERCICIOS 125

Implemente una lista enlazada circular y pruébela, ya que será necesaria


para la solución del siguiente ejercicio.

7. Utilice la implementación de la lista enlazada circular del ejercicio an-


terior para resolver el problema de Josephus descrito en el texto. Para
ello:

a) Considere que existen N soldados. Para cada uno de los cuales,


deberá solicitar su nombre, por lo que se recomienda que la lista
enlazada circular almacene objetos de la clase String.
b) En cada iteración (determinación de la eliminación de un soldado),
genere un número aleatorio n, donde n ∈ [1 . . . N ].
c) De manera paralela al almacenamiento de los soldados en la lista
enlazada circular, almacene el nombre de los soldados en un arre-
glo9 y genere un número aleatorio m, (distinto a n), de tal forma
que m sea el ı́ndice correspondiente al arreglo de nombres de los
soldados, y servirá para elegir al soldado inicial. Tome en cuenta
que m ∈ [0 . . . N − 1].

8. En base a la descripción y la definición de una lista doblemente enlazada


que se realizó en la sección correspondiente, y considerando lo descrito
en el diagrama de clases UML de la Figura 6.9, implemente una lista
doblemente enlazada.
Para realizar lo anterior, considere la siguiente descripción para los
métodos correspondientes:

insertaAlInicio(elemento) agrega a elemento al inicio de la lista


doblemente enlazada.
insertaAlFinal(elemento) agrega a elemento al fin de la lista doble-
mente enlazada.
eliminaDelInicio() elimina un elemento del inicio de la lista doble-
mente enlazada.
eliminaDelFinal() elimina un elemento del fin de la lista doblemente
enlazada.
9
Puede consultar el Apéndice A para consultar el manejo de arreglos.
126 CAPÍTULO 6. LISTAS ENLAZADAS

estaVacia() regresa verdadero o falso, dependiendo de si la lista do-


blemente enlazada contiene o no elementos respectivamente.
imprime() imprime en la salida estándar (pantalla) los elementos de
la lista doblemente enlazada.
obtenN() regresa el número de elementos actualmente almacenados
en la lista doblemente enlazada.
insertaDespuesDe(e1, e2) inserta al elemento e1 después del ele-
mento e2. Si e1 pudo ser insertado, se debe regresar un valor
entero distinto de cero para notificar éxito. En caso contrario10 , se
deberá regresar cero.
insertaAntesDe(e1, e2) inserta al elemento e1 antes del elemento
e2. Si e1 pudo ser insertado, se debe regresar un valor entero
distinto de cero para notificar éxito. En caso contrario11 , se de-
berá regresar cero.
insertaOrdenado(elemento) inserta al elemento en orden ascenden-
te dentro de la lista doblemente enlazada.

9. En base a la implementación de la lista doblemente enlazada del Ejerci-


cio 8 ¿Qué cambios habrı́a que realizar para la implementación de una
lista doblemente enlazada circular?

10. Considere las siguientes fórmulas:

Promedio n
i=1 xi
xprom = (6.1)
n
Desviación estándar

n
− xprom )2
i=1 (xi
σ= (6.2)
n−1

donde n es el número de elementos en el conjunto de datos y x es


un dato del conjunto.
10
Quizá el elemento e2 ni siquiera exista dentro de la lista doblemente enlazada.
11
Ídem.
6.7. EJERCICIOS 127

Regresión lineal
n
( x y ) − (nxprom yprom )
β1 = n i i 2
i=1
(6.3)
( i=1 xi ) − (nx2prom )

β0 = yavg − β1 xavg (6.4)


Para dos conjuntos de datos A y B, donde n es el número de
elementos en ambos conjuntos, i.e. xi ∈ A y yi ∈ B.
Coeficiente de correlación
  
n( ni=1 xi yi ) − ( ni=1 xi )( ni=1 yi )
r =  n 2    (6.5)
[n( i=1 xi ) − ( ni=1 xi )2 ][n( ni=1 yi2 ) − ( ni=1 yi )2 ]

Utilizando la implementación de la lista doblemente enlazada del Ejer-


cicio 8, escriba un programa que realice el cálculo de cada una de las
expresiones anteriores.
Sugerencia: formule cada una de las expresiones como un método.
11. Considere el Ejercicio 5 del Capı́tulo 5 y la Figura 6.10, en donde se
tiene una lista enlazada (vertical) con tres nodos, y en cada uno de
ellos, una referencia a una cola de espera representada en dicha figura
por inicio. Utilizando cada una de las colas de espera referidas por la
lista enlazada, implemente el algoritmo de Round robin.
La Figura 6.10 representa un estado determinado de tres colas de es-
pera con números enteros que representan las cantidades a considerar
(quantum).
La atención de los elementos formados en la cola de espera, consiste
en restarle uno al quantum del nodo que se encuentra al inicio de la
cola, y volverlo a formar al final de la misma para atender de manera
análoga a los siguientes nodos. Tome en consideración que, una vez que
el número llega a cero, el nodo correspondientes es eliminado.
La atención se realiza en base a la prioridad de los elementos de la
cola de espera, la cual está representada por los números 1, 2 y 3 de
la lista enlazada, donde 3 y 1 son la prioridad más alta y más baja
respectivamente. No es posible atender a los elementos formados en
un cola de espera cuya prioridad sea menor, si existen elementos que
necesitan ser atendidos en alguna cola de espera de mayor prioridad.
128 CAPÍTULO 6. LISTAS ENLAZADAS

Figura 6.10: Representación de Round robin por niveles de prioridad

El proceso anteriormente descrito continúa, hasta atender o despachar


a todos los nodos formados en las tres colas de espera. Para lo anterior,
considere:

a) Genere un número aleatorio entre 1 y 3, mismo que representará la


prioridad para seleccionar la cola de espera correspondiente.
b) Para cada cola de espera, genere un número aleatorio entre 10 y
50, el cual representará el número de nodos que contendrá la cola
de espera en cuestión.
c) Por cada nodo, genere nuevamente un número aleatorio entre 1 y
500, mismo que representará el quantum asignado a cada nodo.

No olvide construir también una clase de prueba para su implementa-


ción. La salida de su programa puede ser en la salida estándar o en un
archivo de texto.
Capı́tulo 7

Árboles binarios

Concision in style, precision in thought, decision in life.


Victor Hugo

7.1. Definición
Un árbol binario es un conjunto finito de elementos, el cual está vacı́o,
o dividido en tres subconjuntos disjuntos:

1. El primer subconjunto contiene un elemento único llamado raı́z del


árbol.

2. El segundo subconjunto es en sı́ mismo un árbol binario y se le conoce


como subárbol izquierdo del árbol original.

3. El tercer subconjunto es también un árbol binario y se le conoce como


subárbol derecho del árbol original.

Tome en consideración que en un árbol binario, el subárbol izquierdo o


el subárbol derecho podrı́an estar vacı́os.

7.1.1. Representación y conceptos


Respecto a su representación, es común denominar a cada elemento de
un árbol binario como nodo del árbol. La Figura 7.1 muestra una posible
representación para un árbol binario.

129
130 CAPÍTULO 7. ÁRBOLES BINARIOS

Figura 7.1: Abstracción de un árbol binario como una estructura de nodos

Ahora bien, la representación de la Figura 7.1 resultará sumamente útil


para desarrollar e ilustrar los siguientes conceptos:

Si B es la raı́z de un árbol binario, y D es la raı́z del subárbol iz-


quierdo/derecho1 , se dice que B es el padre de D y que D es el hijo
izquierdo/derecho de B.

A un nodo que no tiene hijos, como los nodos A o C de la Figura 7.1,


se le conoce como nodo hoja.

Se dice que un nodo n1 es un ancestro de un nodo n2 (y que n2 es


un descendiente de n1 ) si n1 es el padre de n2 o el padre de algún
ancestro de n2.

Recorrer un árbol de la raı́z hacia las hojas se denomina descender


el árbol, y al sentido opuesto, ascender el árbol.

El nivel de un nodo en un árbol binario se define del modo siguiente:

1. La raı́z del árbol tiene el nivel 0.


2. El nivel de cualquier otro nodo en el árbol es uno más que el nivel
de su nodo padre.
1
Todos los conceptos que sigan esta notación, tienen una naturaleza simétrica debido
a la caracterı́stica inherente a los árboles, y por lo tanto, aplica tanto para el subárbol
izquierdo como para el subárbol derecho.
7.1. DEFINICIÓN 131

La profundidad o altura de un árbol binario, es el máximo nivel de


cualquier hoja en el árbol.

Por otro lado, se dice que un árbol estrictamente binario es aquel en


el que cada nodo que no es hoja, tiene sus subárboles izquierdo y derecho no
vacı́os. En términos coloquiales, un árbol estrictamente binario es un árbol
binario en el que cada nodo, tiene dos hijos o simplemente no tiene, es decir,
no se vale tener un solo hijo: dos o nada.
De lo anterior, observe que un árbol estrictamente binario con n hojas,
siempre contiene 2n-1 nodos (¿Por qué?).
Otro tipo de árboles binarios son los árboles binarios completos. Un árbol
binario completo de profundidad p, es un árbol estrictamente binario que
tiene todas sus hojas en el nivel p.

7.1.2. Operaciones primitivas


Respecto a las operaciones primitivas para un árbol binario, considere lo
siguiente. Sea p una referencia a un nodo cualquiera (nd ) de un árbol binario.
Se definen las siguientes operaciones primitivas sobre dicho árbol:

obtenDato regresa el contenido (datos) de nd cuando se le envı́a el mensaje


correspondiente al objeto p, es decir:
p.obtenDato()
obtenNodoIzquierdo regresa una referencia al hijo izquierdo de nd cuando
se le envı́a el mensaje correspondiente al objeto p, es decir:
p.obtenNodoIzquierdo()
obtenNodoDerecho regresa una referencia al hijo derecho de nd cuando
se le envı́a el mensaje correspondiente al objeto p, es decir:
p.obtenNodoDerecho()
estableceDato asigna un valor especı́fico representado por d a nd, cuando
se le envı́a el mensaje correspondiente al objeto p, es decir:
p.estableceDato(d)
estableceNodoIzquierdo asigna un nodo representado por n, como hijo
izquierdo de nd cuando se le envı́a el mensaje respectivo al objeto p,
es decir:
p.estableceNodoIzquierdo(n)
132 CAPÍTULO 7. ÁRBOLES BINARIOS

estableceNodoDerecho asigna un nodo representado por n, como hijo de-


recho de nd cuando se le envı́a el mensaje respectivo al objeto p, es
decir:
p.estableceNodoDerecho(n)

Por otro lado y siguiendo las consideraciones planteadas con anterioridad,


algunas otras operaciones que podrı́an ser útiles en la implementación de un
árbol binario, son las siguientes:

1. La operación padre regresa una referencia al padre de nd.

2. La operación hermano regresa una referencia al hermano de nd.

3. La operación esIzquierdo regresa true si nd es un hijo izquierdo de


algún otro nodo en el árbol binario, y false en caso contrario.

4. La operación esDerecho regresa true si nd es un hijo derecho de algún


otro nodo en el árbol binario, y false en caso contrario.

Dichas operaciones no son las únicas posibles; sin embargo, son deseables
como los servicios adicionales que podrı́a proporcionar un árbol binario.

Recorridos
Una operación muy común en un árbol binario, es la de recorrer todo el
árbol en un orden especı́fico pero ¿Cuál serı́a el orden natural de recorrido
de un árbol binario? Piense por un momento en ello.
A la operación de recorrer un árbol binario de una forma especı́fica y de
numerar o visitar sus nodos, se le conoce como visitar el árbol, es decir,
procesar el valor o dato de cada uno de los nodos, para realizar algo con él.
En general, se definen tres métodos de recorrido sobre un árbol binario,
y para ello, se deberán tomar en cuenta las siguientes consideraciones:

1. No se necesita hacer nada para un árbol binario vacı́o.

2. Todos los métodos se definen recursivamente.

3. Siempre se recorren la raı́z y los subárboles izquierdo y derecho, la


diferencia radica en el orden en que se visitan.
7.1. DEFINICIÓN 133

Para recorrer un árbol binario no vacı́o en orden previo (orden de pri-


mera profundidad), se ejecutan tres operaciones:

1. Visitar la raı́z.

2. Recorrer el subárbol izquierdo en orden previo.

3. Recorrer el subárbol derecho en orden previo.

Ahora bien, para recorrer un árbol binario no vacı́o en orden (orden


simétrico), se ejecutan tres operaciones:

1. Recorrer el subárbol izquierdo en orden.

2. Visitar la raı́z.

3. Recorrer el subárbol derecho en orden.

Finalmente, para recorrer un árbol binario no vacı́o en orden posterior,


se ejecutan tres operaciones:

1. Recorrer el subárbol izquierdo en orden posterior.

2. Recorrer el subárbol derecho en orden posterior.

3. Visitar la raı́z.

Tome en consideración que los tres recorridos anteriormente descritos,


son únicamente tres posibilidades de las seis posibles, sin embargo, son los
recorridos más comunes.
Los otros tres recorridos posibles se describen a continuación, y a falta de
un mejor nombre, se les denominará recorridos inversos.
Para recorrer un árbol binario no vacı́o en orden previo inverso, se
ejecutan tres operaciones:

1. Visitar la raı́z.

2. Recorrer el subárbol derecho en orden previo inverso.

3. Recorrer el subárbol izquierdo en orden previo inverso.


134 CAPÍTULO 7. ÁRBOLES BINARIOS

Por otro lado, para recorrer un árbol binario no vacı́o en orden inverso,
se ejecutan tres operaciones:

1. Recorrer el subárbol derecho en orden inverso.

2. Visitar la raı́z.

3. Recorrer el subárbol izquierdo en orden inverso.

Finalmente, para recorrer un árbol binario no vacı́o en orden posterior


inverso, se ejecutan tres operaciones:

1. Recorrer el subárbol derecho en orden posterior inverso.

2. Recorrer el subárbol izquierdo en orden posterior inverso.

3. Visitar la raı́z.

7.2. Árbol binario de búsqueda (ABB)


Un árbol binario es una estructura de datos sumamente útil en muchos
aspectos, en particular, cuando deben tomarse decisiones en dos sentidos, en
cada punto de un proceso determinado.
En base a lo anterior, suponga que por alguna razón se desea encontrar
todos los duplicados de una secuencia de números. Para ello, considere lo
siguiente:

1. El primer número de la secuencia, se coloca en un nodo que se ha


establecido como la raı́z de un árbol binario, cuyos subárboles izquierdo
y derecho están inicialmente vacı́os.

2. Cada número sucesivo en la secuencia se compara con el número que


se encuentra en la raı́z del árbol binario. En este momento, se tienen
tres casos a considerar:

a) Si coincide, se tiene un duplicado.


b) Si es menor, se examina el subárbol izquierdo.
c) Si es mayor, se examina el subárbol derecho.
7.2. ÁRBOL BINARIO DE BÚSQUEDA (ABB) 135

3. Si alguno de los subárboles esta vacı́o, el número no es un duplicado y


se coloca en un nodo nuevo en dicha posición del árbol binario.

4. Si el subárbol correspondiente no está vacı́o, se compara el número con


la raı́z del subárbol en cuestión, y se repite todo el proceso nuevamente
con dicho subárbol.

Un árbol binario de búsqueda (ABB) es un árbol binario que no tiene


valores duplicados en los nodos, y además, tiene las siguientes caracterı́sticas:

1. Los valores en cualquier subárbol izquierdo son menores que el valor


en su nodo padre.

2. Los valores en cualquier subárbol derecho son mayores que el valor en


su nodo padre.

Tome en cuenta que un ABB es un árbol binario, pero un árbol binario


no es necesariamente un ABB. En este sentido, todo lo que se ha dicho y
definido para árboles binarios, es aplicable también a los árboles binarios de
búsqueda.

7.2.1. Operaciones primitivas


Respecto a las operaciones primitivas, se definen cuatro operaciones pri-
mitivas para un ABB:

inserta inserta un nuevo nodo al árbol binario de búsqueda. La inserción


se realiza de manera ordenada respecto del elemento a insertar, por lo
que debe existir una relación de orden para dicho elemento.
En resumen, la operación inserta de manera ordenada a elemento en el
ABB cuando el objeto abb recibe el mensaje correspondiente, es decir:
abb.inserta(elemento)

recorre en preorden recorre el ABB no vacı́o en orden previo, de tal


forma que cuando el objeto abb recibe el mensaje:
abb.recorrePreorden()
se imprime en la salida estándar el recorrido en orden previo de abb.
136 CAPÍTULO 7. ÁRBOLES BINARIOS

recorrido en orden recorre el árbol binario de búsqueda no vacı́o en or-


den, de tal forma que cuando el objeto abb recibe el mensaje:
abb.recorreEnorden()
se imprime en la salida estándar el recorrido en orden de abb.
Note cómo un recorrido en orden, como su nombre lo indica, imprime
en la salida estándar los elementos almacenados en el árbol de manera
ascendente.

recorrido en postorden recorre el ABB no vacı́o en orden posterior, de


tal forma que cuando el objeto abb recibe el mensaje:
abb.recorrePostorden()
se imprime en la salida estándar el recorrido en orden posterior de abb.

Para el caso de un árbol binario de búsqueda, también serı́an deseables


otras operaciones, como aquellas que se definieron para los árboles binarios:
primitivas, adicionales y recorridos inversos por ejemplo.
En éste sentido, es importante resaltar que siempre que una operación
no viole la relación de orden intrı́nseca a los ABB, es factible de ser imple-
mentada, aunque la decisión respecto a su implementación, estará en función
directa de su correspondiente aplicación.

7.2.2. Representación
La representación de un árbol binario que se presentó en la Figura 7.1, es
en realidad un árbol binario de búsqueda.
Para complementar dicha abstracción, el diagrama de clases UML de la
Figura 7.2 muestra otro tipo de representación para un ABB desde el punto
de vista del diseño.
Observe cómo ha cambiado por completo la representación del nodo utili-
zado para un árbol binario de búsqueda (NodoABB ), en comparación con el
nodo que se habı́a estado utilizando (NodoG) para las anteriores estructuras
de datos presentadas en el libro.
La clase NodoABB de la Figura 7.2 define los atributos correspondientes
para los subárboles izquierdo y derecho2 , y el dato a almacenar (dato). Adi-
cionalmente, la clase define los servicios que deberá implementar la clase, los
cuales corresponden a los métodos de tipo set y get.
2
Representados por los atributos nodoIzquierdo y nodoDerecho respectivamente.
7.2. ÁRBOL BINARIO DE BÚSQUEDA (ABB) 137

Figura 7.2: Diagrama de clases UML para un árbol binario de búsqueda

Por otro lado, la clase ABB describe la abstracción correspondiente para


la implementación del árbol binario de búsqueda, y define como servicios, las
operaciones primitivas definidas con anterioridad para un ABB.
Tanto la definición en un diagrama de clases UML, como la implementa-
ción correspondiente de los recorridos inversos, se dejan como ejercicio para
el lector.

7.2.3. Implementación
Esta sección presenta la implementación de un ABB en base a dos enfo-
ques respecto a la inserción de elementos:

1. Enfoque recursivo.

2. Enfoque iterativo.

Para ambas implementaciones, se utiliza la clase NodoABB definida en


el Ejemplo 7.1, por lo que será la primera que se describa.
La clase NodoABB define un nodo capaz de almacenar objetos genéricos.
Cada nodo definido por la clase, tiene la forma de cada uno de los nodos de la
Figura 7.1. Observe además que cada objeto instanciado tendrá dos referen-
cias a objetos como él, las cuales representarán referencias hacia el subárbol
izquierdo (nodoIzquierdo) y hacia el subárbol derecho (nodoDerecho).
Los detalles restantes deberı́an resultarle familiares al lector, ya que en
esencia se refieren a un constructor y a los métodos de tipo set y get para
cada uno de los atributos.
138 CAPÍTULO 7. ÁRBOLES BINARIOS

1 /∗ C l a s e que implementa o b j e t o s nodo g e n e r i c o s para un ABB.


2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 c l a s s NodoABB<T extends Comparable<T>>{
5 private NodoABB<T> n o d o I z q u i e r d o ;
6 private T dato ;
7 private NodoABB<T> nodoDerecho ;
8
9 public NodoABB(T d ) {
10 dato = d ;
11 n o d o I z q u i e r d o = nodoDerecho = n u l l ;
12 }
13
14 public void e s t a b l e c e D a t o (T d ) {
15 dato = d ;
16 }
17
18 public T obtenDato ( ) {
19 return dato ;
20 }
21
22 public void e s t a b l e c e N o d o I z q u i e r d o (NodoABB<T> n ) {
23 nodoIzquierdo = n ;
24 }
25
26 NodoABB<T> o b t e n N o d o I z q u i e r d o ( ) {
27 return n o d o I z q u i e r d o ;
28 }
29
30 public void e s t a b l e c e N o d o D e r e c h o (NodoABB<T> n ) {
31 nodoDerecho = n ;
32 }
33
34 NodoABB<T> obtenNodoDerecho ( ) {
35 return nodoDerecho ;
36 }
37 }

Ejemplo 7.1: Definición de la clase NodoABB que permite representar objetos


(nodos) genéricos para un árbol binario de búsqueda

Asegúrese de comprender el Ejemplo 7.1 en su totalidad antes de conti-


nuar.

Enfoque recursivo
Probablemente el enfoque más común para la implementación de un ABB
debido a la naturaleza inherente a la definición de dicha estructura de datos,
sea el enfoque basado en la recursividad.
El Ejemplo 7.2 muestra la definición de la clase ABBr, la cual es en
esencia la implementación de la representación definida en el diagrama de
7.2. ÁRBOL BINARIO DE BÚSQUEDA (ABB) 139

clases UML de la Figura 7.2.


El estudio y la comprensión de la implementación de los métodos de
recorrido para un árbol binario de búsqueda (lı́neas 34-68), se dejan como
ejercicio para el lector.
El método inserta (lı́neas 13-18), crea la raı́z del árbol (lı́nea 15) si el
ABB está vacı́o (lı́nea 14), i.e. no existe. En caso contrario, delega la res-
ponsabilidad de la inserción de elemento dentro de arbol, al método privado
recursivo insertaR (lı́nea 17).
1 /∗ C l a s e que implementa un ABB con o b j e t o s NodoABB .
2 Implementa l o s r e c o r r i d o s en orden , p r e o r d e n y p o s t o r d e n .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s ABBr<T extends Comparable<T>>{
6 private NodoABB<T> a r b o l ;
7
8 public ABBr ( ) {
9 a r b o l = null ;
10 }
11
12 // i n s e r t a un nuevo nodo en e l ABB
13 public void i n s e r t a (T e l e m e n t o ) {
14 i f ( a r b o l == n u l l )
15 a r b o l = new NodoABB<T>( e l e m e n t o ) ;
16 else
17 insertaR ( arbol , elemento ) ;
18 }
19
20 // i n s e r t a e l e l e m e n t o de manera r e c u r s i v a en e l ABB
21 private void i n s e r t a R (NodoABB<T> abb , T e l e m e n t o ) {
22 i f ( e l e m e n t o . compareTo ( abb . obtenDato ( ) ) < 0 ) // s u b a r b o l i z q u i e r d o
23 i f ( abb . o b t e n N o d o I z q u i e r d o ( ) == n u l l )
24 abb . e s t a b l e c e N o d o I z q u i e r d o (new NodoABB<T>( e l e m e n t o ) ) ;
25 else // r e c o r r e e l a r b o l i z q u i e r d o de manera r e c u r s i v a
26 i n s e r t a R ( abb . o b t e n N o d o I z q u i e r d o ( ) , e l e m e n t o ) ;
27 e l s e i f ( e l e m e n t o . compareTo ( abb . obtenDato ( ) ) > 0 ) // s u b a r b o l d e r e c h o
28 i f ( abb . obtenNodoDerecho ( ) == n u l l )
29 abb . e s t a b l e c e N o d o D e r e c h o (new NodoABB<T>( e l e m e n t o ) ) ;
30 else // r e c o r r e e l a r b o l d e r e c h o de manera r e c u r s i v a
31 i n s e r t a R ( abb . obtenNodoDerecho ( ) , e l e m e n t o ) ;
32 }
33
34 public void r e c o r r e P r e o r d e n ( ) {
35 preorden ( arbol ) ;
36 }
37
38 private void p r e o r d e n (NodoABB<T> nodo ) {
39 i f ( nodo == n u l l )
40 return ;
41 System . out . p r i n t ( nodo . obtenDato ( ) + ” ” ) ; // v i s i t a e l nodo
42 p r e o r d e n ( nodo . o b t e n N o d o I z q u i e r d o ( ) ) ; // r e c o r r e e l s u b a r b o l i z q u i e r d o
43 p r e o r d e n ( nodo . obtenNodoDerecho ( ) ) ; // r e c o r r e e l s u b a r b o l d e r e c h o
44 }
45
140 CAPÍTULO 7. ÁRBOLES BINARIOS

46 public void r e c o r r e E n o r d e n ( ) {
47 enorden ( a r b o l ) ;
48 }
49
50 private void e n o r d e n (NodoABB<T> nodo ) {
51 i f ( nodo == n u l l )
52 return ;
53 e n o r d e n ( nodo . o b t e n N o d o I z q u i e r d o ( ) ) ; // r e c o r r e e l s u b a r b o l i z q u i e r d o
54 System . out . p r i n t ( nodo . obtenDato ( ) + ” ” ) ; // v i s i t a e l nodo
55 e n o r d e n ( nodo . obtenNodoDerecho ( ) ) ; // r e c o r r e e l s u b a r b o l d e r e c h o
56 }
57
58 public void r e c o r r e P o s t o r d e n ( ) {
59 postorden ( arbol ) ;
60 }
61
62 private void p o s t o r d e n (NodoABB<T> nodo ) {
63 i f ( nodo == n u l l )
64 return ;
65 p o s t o r d e n ( nodo . o b t e n N o d o I z q u i e r d o ( ) ) ; // r e c o r r e e l s u b a r b o l i z q u i e r d o
66 p o s t o r d e n ( nodo . obtenNodoDerecho ( ) ) ; // r e c o r r e e l s u b a r b o l d e r e c h o
67 System . out . p r i n t ( nodo . obtenDato ( ) + ” ” ) ; // v i s i t a e l nodo
68 }
69 }

Ejemplo 7.2: Definición de la clase ABBr que permite almacenar nodos


(NodoABB) en un árbol binario de búsqueda

Por su parte, la idea del método insertaR (lı́neas 21-32) es verificar la


relación de orden de elemento respecto del dato almacenado en el nodo actual,
y entonces:

Si elemento es menor (lı́nea 22), debe ser insertado en el subárbol iz-


quierdo.

Si el subárbol izquierdo está vacı́o o disponible (lı́nea 23), se crea un


nuevo nodo con elemento como dato y se asigna como hijo izquierdo
(lı́nea 24). Si no está vacı́o (lı́nea 25), se recorre el subárbol izquierdo de
manera recursiva (lı́nea 26) para encontrar la posición apropiada para
elemento dentro del árbol.

Si elemento es mayor (lı́nea 27), debe ser insertado en el subárbol de-


recho.

Si el subárbol derecho está vacı́o o disponible (lı́nea 28), se crea un


nuevo nodo con elemento como dato y se asigna como hijo derecho
(lı́nea 29). Si no está vacı́o (lı́nea 30), se recorre el subárbol derecho de
7.2. ÁRBOL BINARIO DE BÚSQUEDA (ABB) 141

manera recursiva (lı́nea 31) para encontrar la posición apropiada para


elemento dentro del ABB.

La clase de prueba para la clase ABBr del Ejemplo 7.2, se muestra en el


Ejemplo 7.3.
1 /∗ C l a s e de p r u e b a para un ABB implementando con r e c u r s i v i d a d .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 import j a v a . u t i l . Random ;
5
6 public c l a s s PruebaArbol {
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 ABBr<I n t e g e r > abb = new ABBr<I n t e g e r >() ;
9 Random n u m e r o A l e a t o r i o = new Random ( ) ;
10
11 System . out . p r i n t l n ( ”Numeros a i n s e r t a r (0 −50) : ” ) ;
12 f o r ( i n t i = 1 ; i < 1 1 ; i ++){
13 // Numero a l e a t o r i o e n t r e 0 y 50
14 int v a l o r = numeroAleatorio . nextInt (51) ;
15 System . out . p r i n t ( v a l o r + ” ” ) ;
16 abb . i n s e r t a ( v a l o r ) ;
17 }
18
19 System . out . p r i n t l n ( ” \n\ n R e c o r r i d o en p r e orden : ” ) ;
20 abb . r e c o r r e P r e o r d e n ( ) ;
21
22 System . out . p r i n t l n ( ” \n\ n R e c o r r i d o en orden : ” ) ;
23 abb . r e c o r r e E n o r d e n ( ) ;
24
25 System . out . p r i n t l n ( ” \n\ n R e c o r r i d o en p o s t orden : ” ) ;
26 abb . r e c o r r e P o s t o r d e n ( ) ;
27 System . out . p r i n t l n ( ) ;
28 }
29 }

Ejemplo 7.3: Clase de prueba para el árbol binario de búsqueda (recursivo)

La clase PruebaArbol del Ejemplo 7.3, utiliza la clase Random del API
para generar un número aleatorio (lı́nea 14) dentro de un rango especı́fico,
el cual, además de presentarse en la salida estándar (lı́nea 15), representa el
valor a ser insertado dentro del árbol binario de búsqueda (lı́nea 16).
Finalmente, el Ejemplo 7.3 realiza los tres recorridos más convencionales
definidos para un ABB (lı́neas 19-27). Es ampliamente recomendable que el
lector realice varias ejecuciones de la clase PruebaArbol, y corrobore la salida
con inserciones y recorridos realizados a papel y lápiz.
Una posible salida para el Ejemplo 7.3 se muestra en la Figura 7.3.
142 CAPÍTULO 7. ÁRBOLES BINARIOS

Figura 7.3: Una posible salida para el Ejemplo 7.3 y el Ejemplo 7.5

Enfoque iterativo

Un enfoque alternativo para la implementación de la operación de in-


serción en un árbol binario de búsqueda, es utilizar un ciclo en lugar de
recursividad.
El enfoque propuesto es casi tan compacto como el recursivo, pero mucho
más eficiente. La implementación sin recursividad del ABB representado en
la Figura 7.2, se presenta en el Ejemplo 7.4.
Al igual que antes, el estudio y la comprensión de la implementación de
los métodos de recorrido (lı́neas 34-68), se dejan como ejercicio para el lector.
El método inserta (lı́neas 13-32), crea la raı́z del árbol (lı́nea 15) si el
ABB está vacı́o (lı́nea 14), i.e. no existe. En caso contrario, se genera una
referencia alterna (lı́nea 17) para recorrer el árbol (lı́nea 18).
Al igual que antes, la idea es verificar la relación de orden de elemento
respecto del dato almacenado en el nodo actual, y entonces:

Si elemento es menor (lı́nea 19), debe ser insertado en el subárbol iz-


quierdo.

Si el subárbol izquierdo está vacı́o o disponible (lı́nea 20), se crea un


nuevo nodo con elemento como dato y se asigna como hijo izquier-
do (lı́nea 21). Si no está vacı́o (lı́nea 22), se cambia la referencia del
subárbol en cuestión (abb), y se recorre el subárbol izquierdo (lı́nea 23)
para encontrar la posición apropiada para elemento dentro del árbol.
7.2. ÁRBOL BINARIO DE BÚSQUEDA (ABB) 143

Si elemento es mayor (lı́nea 24), debe ser insertado en el subárbol de-


recho.
1 /∗ C l a s e que implementa un ABB con o b j e t o s NodoABB .
2 Implementa l o s r e c o r r i d o s en orden , p r e o r d e n y p o s t r o d e n .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s ABB<T extends Comparable<T>>{
6 private NodoABB<T> a r b o l ;
7
8 public ABB( ) {
9 a r b o l = null ;
10 }
11
12 // i n s e r t a un nuevo nodo en e l ABB
13 public void i n s e r t a (T e l e m e n t o ) {
14 i f ( a r b o l == n u l l )
15 a r b o l = new NodoABB<T>( e l e m e n t o ) ;
16 else {
17 NodoABB<T> abb = a r b o l ;
18 while ( abb != n u l l )
19 i f ( e l e m e n t o . compareTo ( abb . obtenDato ( ) ) < 0 )
20 i f ( abb . o b t e n N o d o I z q u i e r d o ( ) == n u l l )
21 abb . e s t a b l e c e N o d o I z q u i e r d o (new
NodoABB<T>( e l e m e n t o ) ) ;
22 else
23 abb = abb . o b t e n N o d o I z q u i e r d o ( ) ;
24 e l s e i f ( e l e m e n t o . compareTo ( abb . obtenDato ( ) ) > 0 )
25 i f ( abb . obtenNodoDerecho ( ) == n u l l )
26 abb . e s t a b l e c e N o d o D e r e c h o (new NodoABB<T>( e l e m e n t o ) ) ;
27 else
28 abb = abb . obtenNodoDerecho ( ) ;
29 else
30 return ;
31 }
32 }
33
34 public void r e c o r r e P r e o r d e n ( ) {
35 preorden ( arbol ) ;
36 }
37
38 private void p r e o r d e n (NodoABB<T> nodo ) {
39 i f ( nodo == n u l l )
40 return ;
41 System . out . p r i n t ( nodo . obtenDato ( ) + ” ” ) ; // v i s i t a e l nodo
42 p r e o r d e n ( nodo . o b t e n N o d o I z q u i e r d o ( ) ) ; // r e c o r r e e l s u b a r b o l
izquierdo
43 p r e o r d e n ( nodo . obtenNodoDerecho ( ) ) ; // r e c o r r e e l s u b a r b o l
derecho
44 }
45
46 public void r e c o r r e E n o r d e n ( ) {
47 enorden ( a r b o l ) ;
48 }
49
50 private void e n o r d e n (NodoABB<T> nodo ) {
51 i f ( nodo == n u l l )
144 CAPÍTULO 7. ÁRBOLES BINARIOS

52 return ;
53 e n o r d e n ( nodo . o b t e n N o d o I z q u i e r d o ( ) ) ; // r e c o r r e e l s u b a r b o l
izquierdo
54 System . out . p r i n t ( nodo . obtenDato ( ) + ” ” ) ; // v i s i t a e l nodo
55 e n o r d e n ( nodo . obtenNodoDerecho ( ) ) ; // r e c o r r e e l s u b a r b o l d e r e c h o
56 }
57
58 public void r e c o r r e P o s t o r d e n ( ) {
59 postorden ( arbol ) ;
60 }
61
62 private void p o s t o r d e n (NodoABB<T> nodo ) {
63 i f ( nodo == n u l l )
64 return ;
65 p o s t o r d e n ( nodo . o b t e n N o d o I z q u i e r d o ( ) ) ; // r e c o r r e e l s u b a r b o l
izquierdo
66 p o s t o r d e n ( nodo . obtenNodoDerecho ( ) ) ; // r e c o r r e e l s u b a r b o l
derecho
67 System . out . p r i n t ( nodo . obtenDato ( ) + ” ” ) ; // v i s i t a e l nodo
68 }
69 }

Ejemplo 7.4: Definición de la clase ABB que permite almacenar nodos


(NodoABB) en un árbol binario de búsqueda

Si el subárbol derecho está vacı́o o disponible (lı́nea 25), se crea un nue-


vo nodo con elemento como dato y se asigna como hijo derecho (lı́nea
26). Si no está vacı́o (lı́nea 27), se cambia la referencia del subárbol en
cuestión (abb), y se recorre el subárbol derecho (lı́nea 28) para encon-
trar la posición apropiada para elemento dentro del ABB.
Finalmente, si el elemento no fue menor ni mayor, entonces es igual por
tricotomı́a, y no hay nada por hacer mas que terminar (lı́neas 29 y 30).
La clase de prueba para la clase ABB del Ejemplo 7.4, se muestra en el
Ejemplo 7.5.
Al igual que antes, la clase PruebaABB del Ejemplo 7.5 utiliza la clase
Random del API para generar un número aleatorio (lı́nea 14) dentro de un
rango especı́fico, el cual además de presentarse en la salida estándar (lı́nea
15), representa el valor que será insertado dentro del árbol binario de búsque-
da (lı́nea 16).
Finalmente, el Ejemplo 7.5 también realiza los tres recorridos más conven-
cionales definidos para un ABB (lı́neas 19-27). Una vez más, se recomienda
ampliamente que el lector realice varias ejecuciones de la clase PruebaABB,
y corrobore la salida con inserciones y recorridos realizados a papel y lápiz
(ver Figura 7.3).
7.2. ÁRBOL BINARIO DE BÚSQUEDA (ABB) 145

1 /∗ C l a s e de p r u e b a para un ABB implementando con r e c u r s i v i d a d .


2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 import j a v a . u t i l . Random ;
5
6 public c l a s s PruebaABB{
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 ABB<I n t e g e r > abb = new ABB<I n t e g e r >() ;
9 Random n u m e r o A l e a t o r i o = new Random ( ) ;
10
11 System . out . p r i n t l n ( ”Numeros a i n s e r t a r (0 −50) : ” ) ;
12 f o r ( i n t i = 1 ; i < 1 1 ; i ++){
13 // Numero a l e a t o r i o e n t r e 0 y 50
14 int v a l o r = numeroAleatorio . nextInt (51) ;
15 System . out . p r i n t ( v a l o r + ” ” ) ;
16 abb . i n s e r t a ( v a l o r ) ;
17 }
18
19 System . out . p r i n t l n ( ” \n\ n R e c o r r i d o en p r e orden : ” ) ;
20 abb . r e c o r r e P r e o r d e n ( ) ;
21
22 System . out . p r i n t l n ( ” \n\ n R e c o r r i d o en orden : ” ) ;
23 abb . r e c o r r e E n o r d e n ( ) ;
24
25 System . out . p r i n t l n ( ” \n\ n R e c o r r i d o en p o s t orden : ” ) ;
26 abb . r e c o r r e P o s t o r d e n ( ) ;
27 System . out . p r i n t l n ( ) ;
28 }
29 }

Ejemplo 7.5: Clase de prueba para el árbol binario de búsqueda

7.2.4. Eliminación
La eliminación de un elemento de un árbol binario de búsqueda, consiste
básicamente en, dado un elemento a eliminar:

Identificar si dicho elemento se encuentra en el ABB3 , si no existe, se


podrı́a regresar un valor que indique dicha situación y dejar el ABB sin
cambios.

Si el elemento existe en el ABB, existen tres posibilidades:

1. Si el elemento está almacenado en un nodo hoja, la eliminación es


directa.
3
Identificación de duplicados.
146 CAPÍTULO 7. ÁRBOLES BINARIOS

2. Si el elemento está almacenado en un nodo con un solo hijo, el


hijo sustituye al padre y se elimina el nodo correspondiente.
3. Si el elemento está almacenado en un nodo con dos hijos, la regla
que se adoptará será la de sustituirlo por el hijo más a la derecha
del subárbol izquierdo4 .

Observe también que con el proceso de eliminación descrito con anterio-


ridad:

1. Se conserva la relación de orden que debe mantener un ABB.

2. Se podrı́a incurrir en un intento de eliminación de un ABB vacı́o5 . Por


lo que serı́a conveniente tener una forma de verificar dicha situación.

En función de lo anterior, el diagrama de clases UML de la Figura 7.4


complementa el de la Figura 7.2. Observe que se han realizado las siguientes
modificaciones en la clase ABB :

1. La incorporación de un atributo privado numérico (n), con la finalidad


de llevar el control del número de elementos insertados en el ABB.
Respecto a la implementación de éste, se debe considerar:

a) Inicializar explı́citamente dicho atributo a cero en el constructor.


b) Proporcionar únicamente el método de tipo get para dicho atri-
buto: obtenN().

2. Adición del método elimina, el cual realiza la eliminación de elemento


si éste existe en el ABB devolviendo true como caso de éxito; en caso
contrario, regresa false. Respecto a la implementación de este método,
se debe considerar:

a) Seguir las reglas de eliminación descritas con anterioridad.


b) Lanzar la excepción ExcepcionEDVacia ante un intento de elimi-
nación de un ABB vacı́o, tal y como lo describe la relación que se
muestra en el diagrama de clases de la Figura 7.4.

3. Incorporación del método estaVacio, el cual regresa true si el ABB


está vacı́o, y false en caso contrario.
7.3. ÁRBOLES BINARIOS BALANCEADOS (AVL) 147

Figura 7.4: Diagrama de clases UML para un árbol binario de búsqueda con
eliminación

La implementación de las clases descritas en la Figura 7.4, ası́ como su


correspondiente clase de prueba, se dejan como ejercicio para el lector.

7.3. Árboles binarios balanceados (AVL)


Existen diferentes tipos y variaciones de árboles binarios, y una de las
más importantes es la de los árboles binarios balanceados, árboles binarios
de búsqueda balanceados, o simplemente, árboles AVL.

7.3.1. Definición y conceptos


Un árbol binario balanceado6 es un ABB en el que las alturas de los
dos subárboles de cada nodo difieren a lo más en 1.
El balance de un nodo en un árbol binario en general, y de un árbol
AVL en particular, se define como la altura de su subárbol izquierdo menos
la altura de su subárbol derecho7 (vea la Expresión 7.1). Por convención, la
altura de un árbol AVL nulo o vacı́o se define como -1.
4
También existe la regla simétrica del hijo más a la izquierda del subárbol derecho,
pero no se utilizará en el texto.
5
Lo cual, desde el punto de vista de la implementación, generarı́a una excepción.
6
También conocidos simplemente como árboles AVL en honor a sus creadores Adelson-
V elski y Landis.
7
También existe la correspondiente definición simétrica: la altura de su subárbol derecho
menos la altura de su subárbol izquierdo; pero en este texto se adoptará la primera.
148 CAPÍTULO 7. ÁRBOLES BINARIOS

|altura(arbolIzquierdo) − altura(arbolDerecho)| < 2 (7.1)


Durante el proceso de inserción o eliminación puede ocurrir un desbalance,
es decir, que el valor absoluto de la altura de alguno de los nodos del árbol
AVL sea mayor que 1.
En caso de existir desbalance en alguno de los nodos, es decir, una
condición que incumpla lo establecido por la Expresión 7.1, se tiene que
rebalancear el árbol para que éste siga siendo un árbol AVL válido.
En éste sentido, existen cuatro casos que corrigen el balanceo de un árbol
AVL:
Caso 1 : rotación simple derecha.

Caso 2 : rotación simple izquierda.

Caso 3 : rotación doble izquierda derecha.

Caso 4 : rotación doble derecha izquierda.


A continuación se describen dos de estos cuatro casos, debido a que los
otros dos son simétricos, y pueden derivarse fácilmente a partir de los que se
presentan.

7.3.2. Rotación simple


El primer caso de rebalanceo, la rotación derecha también conocida como
rotación simple, se ilustra en la Figura 7.5.
La Figura 7.5 (a) muestra, hasta antes de la inserción del elemento repre-
sentado por el cuadrado rojo (cuadrado más obscuro), un árbol AVL balan-
ceado. El balance puede determinarse gráficamente por los niveles representa-
dos por lı́neas horizontales punteadas8 . Al insertar el elemento representado
por el cuadrado rojo (cuadrado más obscuro), se presenta un desbalance sobre
el nodo B 9 .
Por otro lado, la Figura 7.5 (b) muestra la solución al desbalanceo descrito
en el párrafo anterior. Para visualizarlo mejor, imagine que en la Figura 7.5
(a), en el nodo representado por B existe una polea fija, de tal forma que al
“jalar”z hacia abajo, el subárbol izquierdo de B representado por A sube,
8
El balance para los nodos A y B es 0 y 1 respectivamente.
9
Ahora el balance para los nodos A y B es 1 y 2 respectivamente.
7.3. ÁRBOLES BINARIOS BALANCEADOS (AVL) 149

Figura 7.5: Caso 1: Rotación derecha [Wirth]

mientras que B baja convirtiéndose en el subárbol derecho de A. Note cómo


y pasa de ser subárbol derecho de A, a ser subárbol izquierdo de B.
Para el caso de la rotación izquierda, el proceso es análogo pero de manera
simétrica, al de la rotación derecha.

Ejemplo

Considere el árbol AVL que aparece en la Figura 7.6 (a)10 .


La rotación simple derecha se presenta al insertar los elementos 1 o 3, los
cuales se resaltan y presentan en la Figura 7.6 (b).
El nodo desbalanceado es el que contiene al elemento 8 (¿Por qué?). La
aplicación del Caso 1 de rotación, dará como resultado el árbol AVL que
aparece en la Figura 7.6 (c), dependiendo del elemento (1 o 3) que se haya
insertado, de tal forma que la rotación derecha se realiza sobre el nodo que
contiene al elemento 8 de la Figura 7.6 (b).
Observe cómo la aplicación de la rotación simple corrige el balance general
del árbol, de tal forma que el balance obtenido es casi perfecto.
Antes de continuar, asegúrese de comprender el proceso de rebalanceo
aplicado a la Figura 7.6 y de determinar, por su propia cuenta, el balance de
cada uno de los nodos para los tres incisos.

10
Asegúrese de que en efecto sea un árbol AVL, y determine que el balance de cada uno
de los nodos, coincide con el que se presenta fuera de cada nodo.
150 CAPÍTULO 7. ÁRBOLES BINARIOS

Figura 7.6: Ejemplo de aplicación de la rotación sencilla

7.3.3. Rotación doble


El caso de rebalanceo que implica una rotación doble es un proceso un
poco más elaborado que el de la rotación simple, ya que como su nombre lo
indica, implica dos rotaciones. La rotación doble izquierda derecha se mues-
tran en la Figura 7.7.
Ante una situación como la que se presenta en la Figura 7.7 (a), la solu-
ción está dada por la Figura 7.7 (c). Ahora bien, existe un paso intermedio,
representado por la Figura 7.7 (b) y es el que se explicará a continuación.
La Figura 7.7 (a) muestra, hasta antes de la inserción del elemento re-
presentado por el cuadrado rojo (cuadrado más obscuro), un árbol AVL ba-
lanceado. El balance puede determinarse gráficamente por los niveles repre-
sentados por lı́neas horizontales punteadas11 . Al insertar cualquiera de los
elementos representados por el cuadrado rojo (cuadrado más obscuro), se
presenta un desbalance sobre el nodo C 12 .
11
El balance para los nodos A, B y C es 0, 0 y 1 respectivamente.
12
Ahora el balance para los nodos A, B y C es -1, (1 o -1 según sea el elemento insertado)
y 2 respectivamente.
7.3. ÁRBOLES BINARIOS BALANCEADOS (AVL) 151

Figura 7.7: Caso 3: Rotación doble izquierda derecha (adaptada de [Wirth])


152 CAPÍTULO 7. ÁRBOLES BINARIOS

Figura 7.8: Ejemplo de aplicación de la rotación doble

Aunque C es el nodo desbalanceado, observe que el balance no se corrige si


se realiza una rotación derecha sobre C (asegúrese de comprobarlo). Por otro
lado, note los signos de los balances generados por la inserción del elemento
que provoca el desbalance.
En base a lo anterior, la Figura 7.7 (b) muestra, aunque A no está des-
balanceado, una rotación izquierda sobre A. Note que el balance no se ha
corregido, ya que el balance de A es 0 o 1, el de B es 2 o 1, y el de C 2.
Ahora bien, partiendo de la Figura 7.7 (b), una nueva rotación derecha
sobre C generará el árbol de la Figura 7.7 (c), mejorando significativamente
el balance general de la mayorı́a de los nodos. Asegúrese de comprender la
doble rotación izquierda derecha explicada hasta aquı́ antes de continuar.
El caso de la rotación doble derecha izquierda es simétricamente análogo.

Ejemplo
Para ilustrar la rotación doble, considere el árbol AVL que aparece en la
Figura 7.8 (a). Una vez más, compruebe que dicho árbol es un árbol AVL y
que los balances propuestos son correctos.
7.4. CONSIDERACIONES FINALES 153

La rotación doble se presenta al insertar cualquiera de los elementos 5 o


7 (Figura 7.8 (b)).
La aplicación de la rotación doble dará como resultado el árbol AVL que
aparece en la Figura 7.8 (c) dependiendo del nodo que se haya insertado. Se
deja como ejercicio para el lector generar el paso intermedio y comparar13 .
Una vez más observe los signos de los balances en la rotación simple y en la
doble; y asegúrese de comprender los procesos de balanceo aplicados en los
ejemplos.

7.4. Consideraciones finales


Los árboles binarios tienen muchas y muy variadas aplicaciones. En este
texto sólo se ha presentado una introducción tanto a los conceptos, como a
algunos de los árboles binarios más comunes, pero es importante que el lector
esté consciente, de que los árboles binarios estudiados en este capı́tulo, no
son los únicos tipos de árboles binarios.
Otro tipos de árboles binarios son, por ejemplo:

Árbol perfectamente balanceado.

Árbol rojo negro.

Árbol AA.

Árbol biselado (splay).

Se recomienda ampliamente al lector buscar información relacionada con


al menos, éste tipo de árboles binarios, con la finalidad de complementar y
ampliar de mejor manera tanto sus conocimientos, como su visión de esta
poderosa y útil estructura de datos.

13
El paso intermedio consiste en hacer una rotación izquierda sobre 4 en la Figura 7.8
(b), y posteriormente una rotación derecha sobre 8. Estas dos rotaciones deberán generar
la Figura 7.8 (c).
154 CAPÍTULO 7. ÁRBOLES BINARIOS

Figura 7.9: Representación de un ABB

7.5. Ejercicios
1. Siguiendo las consideraciones descritas en el texto respecto a un ABB,
realice manualmente, es decir, en papel y lápiz, la inserción de la si-
guiente secuencia de elementos:
14, 15, 4, 9, 7, 18, 3, 5, 16, 4, 20, 17

2. Realice lo mismo que en el ejercicio anterior pero con la secuencia de


números invertida, i.e.:
17, 20, 4, 16, 5, 3, 18, 7, 9, 4, 15, 14

3. Basándose en el Ejemplo 7.1, el Ejemplo 7.2, y el Ejemplo 7.4, realice


las modificaciones correspondientes en ambos tipos de implementación
para que, a través de métodos, proporcione solución a lo siguiente:

a) Dadas dos referencias a dos nodos cualesquiera de un árbol binario,


determine si r1 es padre de r2 (vea la Figura 7.9).
b) Dadas dos referencias a dos nodos cualesquiera de un árbol binario,
determine si r2 es hijo de r1 (vea la Figura 7.9).
c) Dada una referencia a un nodo cualquiera de un árbol binario,
determine si dicho nodo es o no un nodo hoja.
d ) Dadas dos referencias a dos nodos cualesquiera de un árbol binario,
determine si r1 es ancestro de r2.
7.5. EJERCICIOS 155

e) Dadas dos referencias a dos nodos cualesquiera de un árbol binario,


determine si r2 es descendiente de r1.
f ) Dada una referencia a un nodo cualquiera de un árbol binario,
determine su nivel.
g) Determinar la altura o profundidad de un árbol binario.
h) Determinar si un árbol binario es o no un árbol estrictamente
binario.
i) Determinar si un árbol binario es o no un árbol binario com-
pleto de profundidad p.
j ) La implementación de las operaciones complementarias:
padre: regresa una referencia al padre de nd.
hermano: regresa una referencia al hermano de nd.
esIzquierdo: regresa true si nd es un hijo izquierdo de algún
otro nodo en el árbol binario, y false en caso contrario.
esDerecho: regresa true si nd es un hijo derecho de algún
otro nodo en el árbol binario, y false en caso contrario.
Donde nd es una referencia a un nodo cualquiera de un árbol
binario.
k ) Determinar el número de nodos de un árbol binario.
l ) La suma14 de todos los nodos del árbol binario.

4. Modifique el Ejemplo 7.3 para que almacene otro tipo de objetos además
de los de la clase Integer. Pruebe con al menos las siguientes clases:

Float.
String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).

5. Realice lo mismo que se pide en el ejercicio anterior, pero para el Ejem-


plo 7.5.

6. Modifique el Ejemplo 7.3 y el Ejemplo 7.5 para que:


14
Se asume que el árbol binario almacena números.
156 CAPÍTULO 7. ÁRBOLES BINARIOS

a) En lugar de insertar números aleatorios en el árbol binario de


búsqueda, solicite al usuario un número de datos n, y después lea
de la entrada estándar cada uno de los n datos, los cuales serán
insertados en el ABB correspondiente.
b) Realice las comprobaciones de los recorridos y árboles generados
manualmente15 .
c) Implemente los recorridos inversos descritos en el texto.

Nota: respecto a los recorridos inversos, resultarı́a sumamente conve-


niente que, previo a la implementación, realizara el diagrama de clases
UML correspondiente, basándose en el de la Figura 7.2.

7. En base a las consideraciones planteadas en el texto respecto a la elimi-


nación de un ABB y al diagrama de clases UML de la Figura 7.4, imple-
mente la eliminación de elementos para un árbol binario de búsqueda,
ası́ como las modificaciones correspondientes mostradas en dicho dia-
grama.

8. Dada la siguiente secuencia de números: 4, 5, 7, 2, 1, 3, 6, genere en


papel y lápiz su correspondiente ABB. Se deberá ilustrar paso a paso
el proceso de inserción, y realizar la comprobación de cada árbol con
el programa generado en el Ejercicio 6.

9. Realice lo mismo que en el ejercicio anterior pero con la secuencia de


números invertida, i.e.: 6, 3, 1, 2, 7, 5, 4.

10. Dada la siguiente secuencia de números: 4, 5, 7, 2, 1, 3, 6, genere en


papel y lápiz su correspondiente árbol AVL. Se deberá ilustrar paso a
paso el proceso de inserción y rebalanceo.

11. Realice lo mismo que en el ejercicio anterior pero con la secuencia de


números invertida, i.e.: 6, 3, 1, 2, 7, 5, 4.

12. Dada la siguiente secuencia de números: 8, 9, 11, 15, 19, 20, 21, 7, 3,
2, 1, 5, 6, 4, 13, 14, 10, 12, 17, 16, 18, generar:

a) El correspondiente ABB.
15
Un recorrido en preorden o postorden, le hará saber si el árbol generado es correcto.
Un recorrido en orden no le arrojará información útil al respecto.
7.5. EJERCICIOS 157

b) El correspondiente árbol AVL.


c) El correspondiente ABB pero con la secuencia de números inver-
tida.
d ) El correspondiente árbol AVL pero con la secuencia de números
invertida.

Se deberá ilustrar paso a paso en cada uno de los árboles generados, el


proceso de inserción.
13. Respecto a los cinco ejercicios anteriores:
¿Qué conjeturas puede obtener?
¿En qué tipo de árbol se genera el árbol de altura mı́nima?
¿Cuáles serı́an las ventajas y desventajas de uno y otro esquema
de representación de árbol y por qué?
14. Considere la Figura 7.10 (a), la cual representa un árbol AVL. Utili-
zando papel y lápiz, elimine la siguiente secuencia de elementos:
4, 8, 6, 5, 2, 1, 7
Deberá ilustrar paso a paso:

a) Los balances de cada unos de los nodos.


b) La identificación del nodo a eliminar y el proceso de rebalanceo a
aplicar en caso de ser necesario.
c) El correspondiente árbol AVL.

La solución final al ejercicio se presenta en la Figura 7.10 (b).


15. Genere el diagrama de clases UML que represente el diseño de un árbol
AVL en base a lo expuesto en el texto. Puede basarse en el diagrama
de la Figura 7.4.
La idea de este ejercicio, es que su diseño derive en una implementación.
16. Realice la implementación de un árbol AVL. Tome en consideración
que, además de mantener los elementos ordenados como en un ABB,
en cada inserción deberá verificar el balance de los nodos, y que en caso
de ser necesario, deberá aplicar los mecanismos de rebalanceo descritos
en el texto para asegurar que el árbol generado es siempre, un árbol
AVL.
158 CAPÍTULO 7. ÁRBOLES BINARIOS

(a) AVL (b) Solución

Figura 7.10: Eliminación en un árbol AVL (adaptada de [Wirth])

17. En el texto se discuten los aspectos de los árboles AVL relacionados


con el rebalanceo después de la inserción. Sin embargo, ¿qué pasa con
la eliminación de elementos?
Apoyándose en el texto y en el ejercicio anterior, analice y resuelva
los aspectos implicados en la eliminación de nodos en un árbol AVL,
e implemente una operación que permita realizar la eliminación de un
elemento. Asegúrese de mantener siempre un árbol AVL.
Apéndice A

Java

Java and C++ make you think that the new ideas are like the
old ones. Java is the most distressing thing to hit computing
since MS-DOS.
Alan Curtis Kay

El lenguaje de programación Java1 es amado por muchos y odiado por


otros y no participaré en un debate infructuoso. Sólo diré que, como casi
todo en la vida, Java tiene sus ventajas y desventajas, pero lo que es in-
negable, es que es un lenguaje de programación ampliamente difundido y
utilizado, y en ese sentido, resulta importante conocerlo y familiarizarse con
él, independientemente de las posturas ideológicas y preferencias personales.
El presente apartado, no es un tratado del lenguaje, es más, ni siquiera
se acerca a un resumen, es más bien un compendio de referencia a la mano
para el lector familiarizado con algún lenguaje de programación, pero debe
quedar claro que su objetivo no es el de enseñar el lenguaje de programación
Java.
La referencia obligada para este apéndice y para todo el libro en general
es el API (Application Programming Interface) que, al momento de escribir
este libro corresponde a la versión 72 .

1
Java es una marca registrada de Oracle Corporation.
2
http://docs.oracle.com/javase/7/docs/api/

159
160 APÉNDICE A. JAVA

A.1. Orı́genes y caracterı́sticas


Java es un lenguaje de programación originalmente desarrollado por Ja-
mes Gosling cuando trabajaba en la desaparecida empresa Sun Microsystems,
la cual fue adquirida por Oracle Corporation en el año 2009.
El lenguaje fue publicado oficialmente en 1995 y deriva su sintaxis de C y
C++, pero cuenta con menos facilidades de bajo nivel que cualquiera de ellos.
Sin embargo, las aplicaciones o programas de Java son generalmente compi-
ladas a bytecodes, los cuales pueden procesarse en cualquier máquina virtual
Java (JVM) sin importar la arquitectura de la computadora, permitiendo
ası́ una mayor portabilidad del software.
Java es un lenguaje de programación de propósito general, concurrente,
basado en clases, y orientado a objetos, diseñado especı́ficamente para tener
tan pocas dependencias de implementación como fuera posible. Su intención
es permitir que los desarrolladores de aplicaciones escriban el programa una
vez, y lo ejecuten en cualquier dispositivo (WORA, Write Once, Run Anyw-
here).

A.2. Estructura general de una clase


La estructura general de la definición de una clase en Java es como la que
se muestra en el Ejemplo A.1.
Las lı́neas 1-5 muestran el uso de comentarios, se recomienda ampliamente
iniciar la definición de cada clase con un comentario que describa el propósito
de la clase. También es posible definir comentarios de la forma que se muestra
en las lı́neas 10, 13, 17, y 18.
La lı́nea 6 muestra la definición de paquetes. Un paquete es un conjunto de
clases relacionadas, y la palabra reservada package, especifica a qué paquete
pertenecen todas las clases definidas en el archivo fuente.
En Java es posible definir más de una clase por archivo, pero sólo una de
ellas puede ser pública, y su nombre (identificador) debe corresponder con el
nombre del archivo que la contiene. Para el caso del Ejemplo A.1, el nombre
de la clase es EstructuraJava y el del archivo es EstructuraJava.java.
La lı́nea 7 muestra la importación de clases. Las clases se encuentran
ubicadas en paquetes dentro del API, y cuando una clase utiliza alguna clase
definida en algún otro paquete, ésto se especifica con la palabra reservada
import.
A.2. ESTRUCTURA GENERAL DE UNA CLASE 161

1 /∗ E s t r u c t u r a g e n e r a l de una c l a s e en Java .
2 Es r e c o m e n d a b l e i n i c i a r l a d e f i n i c i o n de cada c l a s e con
3 un c o m e n t a r i o que d e s c r i b a e l p r o p o s i t o de l a c l a s e .
4 @autor Ricardo Ruiz R o d r i g u e z
5 ∗/
6 package p a q u e t e ;
7 import p a q u e t e . C l a s e ;
8
9 public c l a s s E s t r u c t u r a J a v a {
10 // D e f i n i c i o n d e l ( l o s ) a t r i b u t o ( s )
11 NombreClase o b j e t o ;
12
13 // D e f i n i c i o n d e l ( l o s ) c o n s t r u c t o r ( e s )
14 EstructuraJava () {
15 }
16
17 // D e f i n i c i o n d e l ( l o s ) metodo ( s ) , l o s c u a l e s puede
18 // s e r p u b l i c o s , p r o t e g i d o s o p r i v a d o s
19 public ClaseR metodo ( ) {
20 }
21 }

Ejemplo A.1: Estructura general de una clase en Java


Las clases se definen por medio de la cláusula class y un identificador.
En general, Java define tres niveles de acceso:

1. public (público) hace que una clase, método o atributo sea accesible
por cualquier otra clase.

2. protected (protegido) hace a un método o atributo accesible única-


mente por las clases del mismo paquete, o por subclases de la clase.

3. private (privado) hace a un método o atributo accesible únicamente


desde su propia clase.

La lı́nea 11 muestra la definición de atributos, los cuales están compuestos


por un identificador para el objeto (objeto), y el nombre de la clase de la que
se derivará. La definición de todos los atributos necesarios para la clase,
siguen la misma estructura.
Las lı́neas 14-15 muestran la definición de constructores. En la instancia-
ción, los objetos se construyen, por lo que no es posible crear un nuevo objeto
sin un constructor. Un constructor es el código que se procesa cuando se
crea un objeto a través de la cláusula new. La construcción de un objeto
es un mecanismo mucho más elaborado del que aquı́ se describe, pero la
explicación a detalle queda fuera de los alcances de este apéndice.
162 APÉNDICE A. JAVA

Finalmente, las lı́neas 19-20, muestran la definición de métodos. Los méto-


dos siguen los niveles de acceso descritos con anterioridad, pueden o no regre-
sar un objeto de alguna clase (ClaseR) o un tipo de dato primitivo, y puede
haber tantos, como servicios quiera proporcionar la clase que los define.
En las secciones siguientes se describirán algunos elementos adicionales
del lenguaje.

A.3. Bienvenid@ a Java


El Ejemplo A.2 muestra el primer programa en Java que se discutirá, y
el más simple que es posible hacer.
1 /∗ Ejemplo de B i e n v e n i d o a Java ( v e r s i o n 1 . 0 ) .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s B i e n v e n i d o 1 {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 System . out . p r i n t l n ( ” Bienvenid@ a Java ! ” ) ;
7 }
8 }

Ejemplo A.2: Primer programa en Java (versión 1.0)

La lı́nea 5 del Ejemplo A.2, muestra el punto de entrada de cualquier


programa en Java: el método main. Éste método siempre tendrá la forma
que se muestra, la cual se conoce en general como la firma del método. La
explicación y los detalles de args se realizarán en la Sección A.5.4.
En Java sólo es posible enviar mensajes a objetos que definan métodos
públicos; sin embargo, la clase Bienvenido1 del Ejemplo A.2 establece que su
método main es static, lo cual quiere decir que puede ser invocado (llamado)
sin una instancia especı́fica que lo reciba3 .
Finalmente, la forma más común de imprimir mensajes en la salida estándar
(pantalla), es la que aparece en la lı́nea 6 del Ejemplo A.2. System es una cla-
se de servicios que tiene Java, y entre dichos servicios está el método println
del objeto out, el cual recibe como argumento un objeto que representa una
cadena (String), y lo envı́a a la salida estándar imprimiendo un salto de lı́nea
al final4 . La salida del Ejemplo A.2 se muestra en la Figura A.1.
3
De hecho ésto es un mecanismo que utiliza Java para poder generar clases de utilerı́as
o servicios, sin embargo su uso deberı́a ser minimizado, ya que se incurre en el estilo de la
programación estructurada al utilizar dichas clases como bibliotecas de funciones.
4
Para más detalles consulte por favor el API de Java.
A.3. BIENVENID@ A JAVA 163

Figura A.1: Salida del Ejemplo A.2

Figura A.2: Salida del Ejemplo A.4

1 /∗ Ejemplo de B i e n v e n i d o a Java ( v e r s i o n 1 . 1 ) .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s B i e n v e n i d o 2 {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 System . out . p r i n t ( ” Bienvenid@ ” ) ;
7 System . out . p r i n t l n ( ” a Java ! ” ) ;
8 }
9 }

Ejemplo A.3: Primer programa en Java (versión 1.1)

Una versión ligeramente modificada del Ejemplo A.2 se muestra en el


Ejemplo A.3.
Observe que ahora en la lı́nea 6 se ha cambiado el método println por
el método print. Éste último hace lo mismo que el método println, con la
diferencia de que el método print no imprime un salto de lı́nea al final, de
ahı́ que se haya dejado un espacio al final de la cadena Bienvenid@. La salida
del Ejemplo A.3 es idéntica a la que se muestra en la Figura A.1.
1 /∗ Ejemplo de B i e n v e n i d o a Java ( v e r s i o n 1 . 2 ) .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s B i e n v e n i d o 3 {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 System . out . p r i n t l n ( ” Bienvenid@ \ na \ nJava ! ” ) ;
7 }
8 }

Ejemplo A.4: Primer programa en Java (versión 1.2)

Un tercera variación del Ejemplo A.2 se muestra en el Ejemplo A.4, el


cual muestra el uso de la secuencia de escape \n para introducir un salto de
lı́nea en cualquier parte de la cadena que se desea imprimir. La salida del
Ejemplo A.4 se muestra en la Figura A.2.
164 APÉNDICE A. JAVA

Finalmente se presenta el Ejemplo A.5, el cual hace uso de la función


printf para imprimir un mensaje en la pantalla. Aquellos lectores que es-
ten familiarizados con el lenguaje de programación C se sentirán cómodos
utilizando dicha función.
1 /∗ Ejemplo de B i e n v e n i d o a Java ( v e r s i o n 1 . 3 ) .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s B i e n v e n i d o 4 {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 System . out . p r i n t f ( ” % s \n % s \n % s \n” , ” Bienvenid@ ” , ” a ” , ” Java ! ” ) ;
7 }
8 }

Ejemplo A.5: Primer programa en Java (versión 1.3)


Sin entrar mucho en detalles, sólo se indicará que %s es un especificador
de formato que le indica a la función printf imprima una cadena, misma que
tomará después de la primera coma (,). Es importante señalar que por cada
especificador de formato (tres en el ejemplo) debe existir su correspondiente
cadena. La salida del Ejemplo A.5 es idéntica a la que se muestra en la Figura
A.2.

A.4. Compilación
Existen diferentes entornos de programación o (IDE) que pueden ser utili-
zados para desarrollar programas en Java, como JavaBeans, JCreator, Eclip-
se, etc., se recomienda al lector buscar y familiarizarse con alguno de ellos, o
con algún otro IDE que sea de su preferencia.
Esta sección describe muy brevemente los pasos para la compilación desde
la lı́nea de comandos, ya que los programas de ejemplo de todo el libro,
pueden ser visualizados y editados en cualquier editor de texto, y compilados
con el compilador de Java (javac) sin necesidad de un IDE. Es importante
aclarar que se asume que se tiene instalado el jdk. Si tiene dudas al respecto,
consulte los detalles de instalación del jdk en la página oficial de Java.
Para saber la versión de Java que tiene instalada, puede escribir desde la
lı́nea de comandos:
$ javac -version
Lo que deberá aparecer en su pantalla es la versión correspondiente del
compilador; si aparece un mensaje distinto, es probable que no tenga insta-
lado el jdk o que las rutas de acceso no sean las correctas.
A.5. EJEMPLOS SELECTOS 165

Para compilar el programa del Ejemplo A.2, tiene que escribir:

$ javac Bienvenido1.java

Cuando un programa se compila siguiendo la idea planteada, se buscarán


todas las clases requeridas dentro del directorio de trabajo actual, mismo que
corresponde al directorio en donde se haya ejecutado el compilador de Java.
Si alguna clase tuviera algún problema, se reportará dicha situación antes de
volver a visualizar el sı́mbolo del sistema ($); en otro caso, se visualiza de
manera casi inmediata el sı́mbolo del sistema, y observará que se han creado
nuevos archivos, los cuales corresponden a los nombres de las clases pero
con extensión class, mismos que representan los bytecodes que interpreta la
máquina virtual de Java.
La máquina virtual de Java o JVM (Java Virtual Machine) es la encarga-
da de interpretar y procesar los bytecodes que representan las instrucciones
del programa compilado. La JVM está representada por el programa java,
por lo que, para ejecutar un programa en Java, se debe proporcionar a la
JVM la clase principal, la cual es la que contiene el método main:

$ java Bienvenido1

La salida del comando anterior, deberı́a ser el mensaje presentado en la


Figura A.1.

A.5. Ejemplos selectos


Es imposible presentar, ya no digamos en un capı́tulo, sino en un libro
completo un conjunto de ejemplos representativos para cualquier lenguaje
de programación; sin embargo, en esta sección se han seleccionado algunos
ejemplos que pudieran ser de utilidad para la familiarización con el lenguaje
de programación Java, y para comprender los ejemplos desarrollados en el
libro.

A.5.1. Lectura de datos


Una de las tareas más comunes para cualquier programa, es la lectura
de datos desde la entrada estándar (teclado). Para los programas del libro
que utilizan entrada de datos, se sugiere el enfoque que se presenta en el
166 APÉNDICE A. JAVA

Ejemplo A.6, el cual no hace uso de una interfaz gráfica de usuario (GUI) para
mantener la atención en los aspectos relevantes, y también para mantener
más cortos los programas.
El Ejemplo A.6 muestra en la lı́nea 4 la importación de la clase Scanner
del paquete java.util, el cual es un paquete con diversas utilerı́as5 .
Las instancias de la clase Scanner (como entrada) proporcionan diferen-
tes servicios, entre ellos el método nextInt, el cual se encarga de obtener el
siguiente número entero de la entrada estándar (lı́neas 17 y 19).
1 /∗ Ejemplo de l e c t u r a de d a t o s .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 import j a v a . u t i l . S c a n n e r ;
5
6 public c l a s s L e c t u r a {
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 // Se c r e a un o b j e t o ( i n s t a n c i a ) de l a c l a s e Scanner (API)
9 // para o b t e n e r ( l e e r ) d a t o s de l a e n t r a d a e s t a n d a r
10 S c a n n e r e n t r a d a = new S c a n n e r ( System . i n ) ;
11
12 I n t e g e r numero1 ;
13 I n t e g e r numero2 ;
14 I n t e g e r suma ;
15
16 System . out . p r i n t ( ” Primer e n t e r o ? : ” ) ; // prompt
17 numero1 = e n t r a d a . n e x t I n t ( ) ; // l e e un numero e n t e r o
18 System . out . p r i n t ( ” Segundo e n t e r o ? : ” ) ; // prompt
19 numero2 = e n t r a d a . n e x t I n t ( ) ; // l e e o t r o numero e n t e r o
20
21 suma = numero1 + numero2 ; // R e a l i z a l a suma
22 // P r e s e n t a e l r e s u l t a d o
23 System . out . p r i n t f ( ” % d + % d = % d\n” , numero1 , numero2 , suma ) ;
24 }
25 }

Ejemplo A.6: Lectura de datos desde la entrada estándar

Observe que el objeto entrada ha sido creado (lı́nea 10) utilizando el


objeto in de la clase System que, por decirlo de una manera simple, es la parte
complementaria de System.out. Éste es el mecanismo usual para la entrada
de datos. También note que han sido declarados tres objetos pertenecientes
a la clase Integer (lı́neas 12-14).
Java también maneja lo que se conoce como tipos de datos primitivos
al estilo de C, la clase Integer es en realidad una envoltura (wrapper) para
el tipo de dato int. Una posible salida para el Ejemplo A.6 se muestra en la
Figura A.3.
5
Se recomienda en este momento echar un vistazo en el API de Java para tener una
A.5. EJEMPLOS SELECTOS 167

Figura A.3: Salida del Ejemplo A.6

A.5.2. Estructuras de control


Java incorpora las estructuras de control tradicionales, las cuales se asu-
men conocidas por el lector. Esta sección presenta un resumen incompleto
de las estructuras de control de selección y de repetición, únicamente para
tenerlas como una referencia inmediata.

Estructuras de selección
El Ejemplo A.7 muestra el uso de la estructuras de selección if y los
operadores relacionales (lı́neas 20-31), ası́ como el uso de la estructura de
selección if-else (lı́neas 33-38).
Los lectores familiarizados con el lenguaje de programación C notarán
que tanto las estructuras de selección, como los operadores relacionales son
idénticos en Java, pero a diferencia de C, sı́ existe el tipo booleano, por lo que
en Java es válido decir que una expresión se evalúa como verdadera o falsa
según sea el caso.
Note que las lı́neas 34 y 36 han hecho uso de una expresión de concate-
nación de cadenas de la forma:
objeto + cadena + objeto
lo cual es bastante común en Java, y lo que hace es precisamente conca-
tenar las cadenas por medio del operador +. Note que aunque los objetos,
como en el caso del ejemplo no son cadenas, Java incorpora en la mayorı́a de
sus clases el método toString, el cual se encarga de regresar una representa-
ción de cadena del objeto correspondiente.
De hecho se recomienda que, en la medida de lo posible, las clases definidas
por el usuario definan el método toString con la intención de mantener una
compatibilidad con este tipo de situaciones. Tome en cuenta que aunque el
método toString es heredado de la clase Object (la clase base en Java), es
recomendable definir un comportamiento particular para una clase especı́fica.
Note también que no existe un llamado explı́cito del método, sino un llamado
mejor idea de las clases que contiene el paquete java.util.
168 APÉNDICE A. JAVA

implı́cito, el cual se realiza a través del operador de concatenación de cadenas


+.

1 /∗ Ejemplo de l a e s t r u c t u r a de s e l e c c i o n if y
2 l o s operadores r e l a c i o n a l e s .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 import j a v a . u t i l . S c a n n e r ;
6
7 public c l a s s I f {
8 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
9 S c a n n e r e n t r a d a = new S c a n n e r ( System . i n ) ;
10
11 I n t e g e r numero1 , numero2 ;
12
13 System . out . p r i n t ( ” Primer e n t e r o ? : ” ) ;
14 numero1 = e n t r a d a . n e x t I n t ( ) ;
15 System . out . p r i n t ( ” Segundo e n t e r o ? : ” ) ;
16 numero2 = e n t r a d a . n e x t I n t ( ) ;
17
18 // S i a l g u n a e x p r e s i o n e s verdadera , se procesa l a s e n t e n c ia
19 // System c o r r e s p o n d i e n t e
20 i f ( numero1 == numero2 )
21 System . out . p r i n t f ( ” % d == % d\n” , numero1 , numero2 ) ;
22 i f ( numero1 != numero2 )
23 System . out . p r i n t f ( ” % d != % d\n” , numero1 , numero2 ) ;
24 i f ( numero1 < numero2 )
25 System . out . p r i n t f ( ” % d < % d\n” , numero1 , numero2 ) ;
26 i f ( numero1 > numero2 )
27 System . out . p r i n t f ( ” % d > % d\n” , numero1 , numero2 ) ;
28 i f ( numero1 <= numero2 )
29 System . out . p r i n t f ( ” % d <= % d\n” , numero1 , numero2 ) ;
30 i f ( numero1 >= numero2 )
31 System . out . p r i n t f ( ” % d >= % d\n” , numero1 , numero2 ) ;
32
33 i f ( numero1 % numero2 == 0 )
34 System . out . p r i n t l n ( numero1 + ” e s d i v i s i b l e por ” + numero2 ) ;
35 e l s e i f ( numero2 % numero1 == 0 )
36 System . out . p r i n t l n ( numero2 + ” e s d i v i s i b l e por ” + numero1 ) ;
37 else
38 System . out . p r i n t l n ( ” Los numeros no son d i v i s i b l e s e n t r e s i ” ) ;
39 }
40 }

Ejemplo A.7: Uso de la estructura de selección if y de los operadores


relacionales

Una posible salida para el Ejemplo A.7, se muestra en la Figura A.4. Por
otro lado, la Tabla A.1 muestra la lista de operadores relacionales utilizados
en Java.
A.5. EJEMPLOS SELECTOS 169

Figura A.4: Salida del Ejemplo A.7

Operador Descripción
== Igual que
!= Distinto de
< Menor estricto que
> Mayor estricto que
<= Menor igual que
>= Mayor igual que

Tabla A.1: Operadores relacionales en Java

Estructuras de repetición
Las estructuras de repetición while, do-while y for se muestran, respec-
tivamente en los Ejemplos A.8, A.9 y A.10.
1 /∗ Ejemplo d e l c i c l o w h i l e .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s While {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 int contador = 1 ;
7 while ( c o n t a d o r <= 1 0 ) {
8 System . out . p r i n t f ( ” % d ” , c o n t a d o r ) ;
9 c o n t a d o r ++;
10 }
11 System . out . p r i n t l n ( ) ;
12 }
13 }

Ejemplo A.8: Estructura de repetición while


Los Ejemplos A.8, A.9 y A.10 se explican por sı́ mismos. Note que en los
tres ejemplos se ha utilizado el tipo de dato primitivo int para la variable de
control contador.
La salida de los tres ejemplos es la misma, y se muestra en la Figura A.5.
170 APÉNDICE A. JAVA

Figura A.5: Salida de los Ejemplos A.8, A.9 y A.10

1 /∗ Ejemplo d e l c i c l o do−w h i l e .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s DoWhile {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 int contador = 1 ;
7
8 do{
9 System . out . p r i n t f ( ” % d ” , c o n t a d o r ) ;
10 c o n t a d o r ++;
11 } while ( c o n t a d o r <= 1 0 ) ;
12
13 System . out . p r i n t l n ( ) ;
14 }
15 }

Ejemplo A.9: Estructura de repetición do-while

1 /∗ Ejemplo d e l c i c l o f o r .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s For {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 f o r ( i n t c o n t a d o r = 1 ; c o n t a d o r <= 1 0 ; c o n t a d o r++)
7 System . out . p r i n t f ( ” % d ” , c o n t a d o r ) ;
8 System . out . p r i n t l n ( ) ;
9 }
10 }

Ejemplo A.10: Estructura de repetición for

A.5.3. Arreglos
El Ejemplo A.11 muestra la creación, recorrido e impresión de un arreglo
de enteros (int).
La lı́nea 7 define al objeto arreglo como un arreglo de enteros. Observe
que el objeto es creado (new), con un tamaño especı́fico (diez).
Adicionalmente se definen también un par de variables:

1. Un valor inicial: valor (lı́nea 8).

2. Un incremento: incremento (lı́nea 9)


A.5. EJEMPLOS SELECTOS 171

Figura A.6: Salida del Ejemplo A.11

Los arreglos en Java, al ser creados y definidos como objetos, tienen de-
finido el atributo público length, el cual almacena la longitud del arreglo.
Dicha propiedad es la que se utiliza como expresión condicional en los ciclos
for de las lı́neas 12 y 17 respectivamente.
1 /∗ Ejemplo de a r r e g l o s .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 public c l a s s A r r e g l o {
5 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
6 // Se d e f i n e un a r r e g l o de d i e z e n t e r o s
7 i n t [ ] a r r e g l o = new i n t [ 1 0 ] ;
8 int v a l o r = 1974;
9 int incremento = 2 2 ;
10
11 // Se i n i c i a l i z a e l a r r e g l o
12 f o r ( i n t i = 0 ; i < a r r e g l o . l e n g t h ; i ++)
13 a r r e g l o [ i ] = valor + incremento ∗ i ;
14
15 // Se imprime e l a r r e g l o
16 System . out . p r i n t l n ( ” V a l o r e s g e n e r a d o s y almacenados en e l a r r e g l o : ” ) ;
17 f o r ( i n t i = 0 ; i < a r r e g l o . l e n g t h ; i ++)
18 System . out . p r i n t ( a r r e g l o [ i ] + ” ” ) ;
19 System . out . p r i n t l n ( ) ;
20 }
21 }

Ejemplo A.11: Arreglo de enteros (int)


El primer ciclo recorre el arreglo para inicializar y asignar los valores al
arreglo en función de valor, incremento y la variable de control i.
Por otro lado, el segundo ciclo realiza un recorrido tradicional para im-
presión en la salida estándar. La salida del Ejemplo A.11 se muestra en la
Figura A.6.

A.5.4. Argumentos en la lı́nea de comandos


El Ejemplo A.12 muestra la forma de procesar los argumentos en la invo-
cación de un programa, lo cual resultará útil y necesario en diversas ocasiones,
y para algunos de los ejercicios planteados en el libro.
El objeto args es un arreglo de cadenas (lı́nea 6), por lo que en la lı́nea 7
se verifica si se han proporcionado o no argumentos en la lı́nea de comandos;
172 APÉNDICE A. JAVA

Figura A.7: Salida del Ejemplo A.12 sin argumentos

Figura A.8: Salida del Ejemplo A.12 con argumentos

en caso de que no, se reporta en la lı́nea 8 (Figura A.7), en caso de que sı́,
se procesa la lista de argumentos con un ciclo (lı́nea 10), y se imprime en
la salida estándar, la lista de argumentos proporcionados, mismos que están
almacenados en el arreglo de cadenas args (Figura A.8).
1 /∗ Ejemplo de uso de p r o c e s a m i e n t o de argumentos en l a l i n e a
2 de comandos a t r a v e s d e l o b j e t o a r g s .
3 @autor Ricardo Ruiz R o d r i g u e z
4 ∗/
5 public c l a s s MainArgs {
6 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
7 i f ( args . length < 1)
8 System . out . p r i n t l n ( ”No hay argumentos para p r o c e s a r ” ) ;
9 else
10 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++)
11 System . out . p r i n t f ( ” Argumento[ % d ] = % s \n” , i , a r g s [ i ] ) ;
12 }
13 }

Ejemplo A.12: Procesamiento de argumentos en la lı́nea de comandos

A.5.5. Excepciones
Las excepciones permiten una abstracción sobre el mecanismo de manejo
de errores ligeros o condiciones que un programa pudiera estar interesado en
atrapar y procesar.
A.5. EJEMPLOS SELECTOS 173

Figura A.9: Relación UML de la jerarquı́a de la clases Exception en Java

La idea subyacente en las excepciones es separar el manejo de éste tipo de


condiciones o errores, de la lógica de funcionamiento subyacente al programa.
Una excepción es una situación anormal en la lógica de ejecución esperada
por un programa, como el intentar clonar un objeto que no tiene implemen-
tado el mecanismo de clonación por ejemplo, manejar un formato de datos
inadecuado para algún especificador, intentar realizar una operación de E/S
en un canal cerrado, intentar acceder a elementos de una estructura de datos
que no contiene elementos, entre muchı́simas otras más.
En la práctica, es posible definir en Java clases que gestionen excepciones
o errores de una aplicación en particular. De hecho, una de las intenciones
de las excepciones es la de, ante una problemática determinada, tratar de
solucionarla en la medida de lo posible para continuar con el programa o
aplicación activos, y no la de terminar con la primera dificultad que se pre-
sente.
Un manejo completo y robusto de excepciones es una labor que, si bien
su dominio no requiere de lustros, no es una tarea trivial y queda fuera de
los alcances de este texto.
Para los ejemplos desarrollados en el libro, se hará uso de la excepción
definida por la clase RuntimeException del API, la cual maneja el conjunto
174 APÉNDICE A. JAVA

de excepciones generadas durante la ejecución.


La clase Throwable es la clase base de todas las excepciones que pueden ser
lanzadas en Java, por lo que la revisión de esta jerarquı́a de clases del API, es
un punto inicial fundamental tanto para la comprensión de las excepciones,
como para su referencia permanente. La relación de la jerarquı́a de clases en
la que se encuentra la clase Exception en el contexto de Java, se expresa en
un diagrama de clases de UML (Unified Modeling Language) de la Figura
A.9.
Por otro lado, el Ejemplo A.13 muestra la definición de una excepción
bastante sencilla pero útil, de hecho, la clase mostrada en dicho ejemplo, es
la que se utiliza para las estructuras de datos desarrolladas en el libro.
1 /∗ Ejemplo de d e f i n i c i o n de e x c e p c i o n .
2 La e x c e p c i o n s e r a l a n z a d a cuando s e haga un i n t e n t o de e l i m i n a c i o n
3 de una e s t r u c t u r a de d a t o s que e s t e v a c i a .
4 La c l a s e RuntimeException e s l a s u p e r c l a s e de l a s e x c e p c i o n e s
5 que pueden s e r l a n z a d a s d u r a n t e l a o p e r a c i o n normal de l a JVM.
6 @autor Ricardo Ruiz R o d r i g u e z
7 ∗/
8 public c l a s s ExcepcionEDVacia extends RuntimeException {
9 public ExcepcionEDVacia ( ) {
10 t h i s ( ” E s t r u c t u r a de d a t o s ” ) ;
11 }
12
13 public ExcepcionEDVacia ( S t r i n g s ) {
14 super ( s + ” v a c i a ” ) ;
15 }
16 }

Ejemplo A.13: Definición de una excepción


Note que la excepción ExcepcionEDVaciaAP es una subclase de la cla-
se RuntimeException. La clase RuntimeException es la super clase de las
excepciones que pueden ser lanzadas durante la ejecución de la JVM.

A.5.6. Genéricos
La definición de jdk 5.0 introdujo nuevas modificaciones y extensiones a
Java, y una de ellas fue el aspecto relacionado con los genéricos (generics).
Los genéricos son en sı́ mismos todo un tema de estudio, pero dado que
se utilizan en la mayorı́a de los ejemplos del libro respecto a la definición de
las estructuras de datos, aquı́ se presenta una exageradamente breve intro-
ducción.
Los genéricos permiten una abstracción sobre los tipos de datos o los
objetos que se procesan, y dentro de sus objetivos se encuentran el eliminar
A.5. EJEMPLOS SELECTOS 175

la ambigüedad latente que existı́a en la conversión forzada de tipos (cast) y


la molestia de su realización, ya que usualmente un programador sabe cual
es el tipo de dato que está procesando cuando utiliza una colección de datos
por ejemplo.
El siguiente fragmento de código, se utilizaba antes de los genéricos:

List lista = new LinkedList();


lista.add("Genericos en Java");
String cadena = (String) lista.get(0);

Con los genéricos el programador pone una marca (clase o tipo de da-
tos en particular), por decirlo de alguna manera, para restringir los datos a
almacenar y recuperar:

List<String> lista = new LinkedList<String>();


lista.add("Genericos en Java");
String cadena = lista.get(0);

El cambio es aparentemente simple pero significativo, ya que evita los


errores intencionales o accidentales en tiempo de ejecución, además de que
permite al compilador hacer una verificación sobre los tipos de datos que
se gestionan. Note que en el segundo fragmento de código, el cast ha sido
eliminado.
El Ejemplo A.14 del Ejercicio 10 muestra el uso de genéricos en el contexto
de colecciones (ArrayList). Es ampliamente recomendable para el lector pro-
fundizar más sobre el tema de genéricos, ya que es una de las caracterı́sticas
de Java más ampliamente difundidas.
176 APÉNDICE A. JAVA

Operador Descripción
/ División (cociente)
% Módulo (residuo)
* Multiplicación
+ Adición
- Sustracción

Tabla A.2: Operadores aritméticos en Java

A.6. Ejercicios
1. Investigue más acerca del concepto de máquina virtual y de los byteco-
des. ¿Fue Java el primer lenguaje de programación en incorporar dichos
conceptos?

2. Para el programa del Ejemplo A.3, pruebe lo que sucede si elimina el


espacio que aparece al final de la cadena Bienvenid@. No olvide volver
a compilar.

3. Considere el Ejemplo A.4 e investigue qué otras secuencias de escape


existen, también infórmese si dichas secuencias coinciden o no con las
que se utilizan en el lenguaje de programación C.

4. En base a lo expuesto para el Ejemplo A.5, investigue qué otros especifi-


cadores de formato existen. Infórmese también si dichos especificadores
coinciden o no con los que se utilizan en el lenguaje de programación
C.

5. Utilice el Ejemplo A.6, y cambie la clase Integer por el tipo primitivo


int. Compruebe si es necesario o no hacer algún otro tipo de cambio
para que el programa funcione.

6. En base a lo descrito en el Ejemplo A.6, modifique dicho ejemplo para


agregar las cuatro operaciones aritméticas: suma, resta multiplicación
y división. Los operadores aritméticos se muestran en la Tabla A.2.
Realice también lo anterior para números enteros representados como
objetos (Integer), y como tipos de datos primitivos.
A.6. EJERCICIOS 177

7. Modifique el Ejemplo A.6 para procesar ahora números Float y Double.


Tome en cuenta que deberá consultar el API para cambiar el método
nextInt por el método apropiado. Investigue también si existen los tipos
de datos primitivos correspondientes (float y double).

8. Modifique el Ejemplo A.11 para que, utilizando el procesamiento de


argumentos en la lı́nea de comandos como el que se hizo en el Ejemplo
A.12, acepte tres argumentos:

a) Tamaño del arreglo (n).


b) Valor inicial (valor ).
c) Incremento (incremento).

Su programa deberá generar un arreglo de n enteros, con un valor


inicial para el primer elemento y un incremento para los demás.
Observe que los elementos de args son cadenas, por lo que tendrá que
consultar el API para utilizar métodos de conversión de cadenas a
números enteros.
Sugerencia: consulte el método parseInt de la clase Integer.

9. Consulte bibliografı́a y la internet para documentarse más acerca del


uso y definición de excepciones en Java.

10. Considere el Ejemplo A.14. Investigue en el API las colecciones, par-


ticularmente la colección ArrayList, y determine el funcionamiento del
programa antes de compilarlo y ejecutarlo en la máquina virtual de
Java.
1 /∗ Ejemplo de una c o l e c c i o n A r r a y L i s t de cadenas .
2 @autor Ricardo Ruiz R o d r i g u e z
3 ∗/
4 import j a v a . u t i l . A r r a y L i s t ;
5
6 public c l a s s C o l e c c i o n {
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 // Con c u a n t o s e l e m e n t o s s e c r e a o r i g i n a l m e n t e e l o b j e t o
cadenas ?
9 A r r a y L i s t <S t r i n g > c a d e n a s = new A r r a y L i s t <S t r i n g >() ;
10
11 c a d e n a s . add ( ” Java ” ) ;
12 c a d e n a s . add ( 0 , ”C++” ) ;
13
14 System . out . p r i n t l n ( ” Contenido d e l A r r a y L i s t c a d e n a s : ” ) ;
15 imprime ( c a d e n a s ) ;
178 APÉNDICE A. JAVA

16
17 c a d e n a s . add ( ” S m a l l t a l k ” ) ;
18 c a d e n a s . add ( ” Java ” ) ;
19 c a d e n a s . add ( ” O b j e c t i v e C ” ) ;
20 System . out . p r i n t l n ( ” Contenido d e l A r r a y L i s t c a d e n a s : ” ) ;
21 imprime ( c a d e n a s ) ;
22
23 c a d e n a s . remove ( ” Java ” ) ;
24 System . out . p r i n t l n ( ” Contenido d e l A r r a y L i s t c a d e n a s : ” ) ;
25 imprime ( c a d e n a s ) ;
26
27 c a d e n a s . remove ( 1 ) ;
28 System . out . p r i n t l n ( ” Contenido d e l A r r a y L i s t c a d e n a s : ” ) ;
29 imprime ( c a d e n a s ) ;
30
31 System . out . p r i n t f ( ” \” Java \” % s e s t a en e l A r r a y L i s t \n” ,
c a d e n a s . c o n t a i n s ( ” Java ” ) ? ” ” : ” no ” ) ;
32 System . out . p r i n t l n ( ”Hay ” + c a d e n a s . s i z e ( ) + ” e l e m e n t o s en e l
ArrayList cadenas ” ) ;
33 }
34
35 // P r e s e n t a l o s e l e m e n t o s d e l A r r a y L i s t cadenas en l a s a l i d a
estandar
36 public s t a t i c void imprime ( A r r a y L i s t <S t r i n g > c a d e n a s ) {
37 for ( S t r i n g elemento : cadenas )
38 System . out . p r i n t ( e l e m e n t o + ” ” ) ;
39 System . out . p r i n t l n ( ) ;
40 }
41 }

Ejemplo A.14: Uso de una colección ArrayList

Compruebe la deducción del funcionamiento con la salida real del pro-


grama.
Bibliografı́a

[Budd] Budd, Timothy A., “An Introduction to Object-Oriented Program-


ming”, Third Edition, Addison Wesley.

[Bracha] Bracha, Gilad, “Generics”, Oracle Corporation,


http://docs.oracle.com/javase/tutorial/extra/generics/ (Julio 2013).

[Deitel] Deitel, H. M. y Deitel, P. J., “Cómo Programar en Java”, Prentice


Hall.

[Kuhn] Kuhn, Thomas Samuel., “The Structure of Scientific Revolutions”,


University of Chicago Press.

[Langsam] Langsam, Yedidyah, Augenstein, M. J. and Tenenbaum, A. M.,


“Estructuras de Datos con C y C++”, Segunda Edición, Prentice Hall
Hispanoamericana.

[Mark] Mark Allen Weiss, “Estructuras de Datos en Java”, Addison Wesley.

[McConnell] McConnell, Steve, “Code Complete”, Second Edition, Micro-


soft.

[Ruiz] Ruiz-Rodrı́guez, Ricardo, “Una Introducción a la Programación Es-


tructurada en C”, El Cid Editor.

[Shalloway] Shalloway, Alan and Trott James R., “Design Patterns Explained
A New Perspective on Object Oriented Design”, Addison Wesley.

[Sierra] Sierra, Kathy and Bates, Bert, “Sun Certified Programmer & Deve-
loper for Java 2 ”, Mc Graw Hill/Osborne.

[Wirth] Wirth, Niklaus, “Algoritmos y Estructuras de Datos”, Prentice Hall.

179
180 BIBLIOGRAFÍA
Índice Analı́tico

árbol binario, 129 RuntimeException, 44, 174


altura, 131 Throwable, 174
ancestro, 130 Unified Modeling Language, 25, 174
balanceado (AVL), 147 Write Once Run Anywhere, 160
completo, 131 bytecodes, 160, 165, 176
de búsqueda, 135 cast, 175
descendiente, 130 information hiding, 11
estrictamente binario, 131 overload, 21, 23
hijo, 130 override, 7, 27, 31
nivel, 130 wrapper, 166
nodo, 129
nodo hoja, 130 abstracción, 2, 4, 6, 8, 10, 33, 42
operaciones primitivas, 131 ADT, 50
padre, 130 de datos, 41
profundidad, 131 excepciones, 172
raı́z, 129 genéricos, 174
recorridos, 130, 132 acoplamiento, 9
en orden, 133, 136 ADT, 41, 95
en orden inverso, 134 complejo, 54
inversos, 133, 156 definición del operador, 42
orden posterior, 133, 136 definición del valor, 42
orden posterior inverso, 134 racional, 44
orden previo, 133, 135 agentes, 4
orden previo inverso, 133 Alan Kay, 9, 13
subárbol derecho, 129 algoritmo, 4
subárbol izquierdo, 129 API, 73, 74, 159, 160, 174
visitar, 132 ArrayList, 175, 177
Abstract Data Type, 41 Comparable, 89
Application Programming Interface, 159 Double, 177
Java Virtual Machine, 165 Float, 177

181
182 ÍNDICE ANALÍTICO

Integer, 166, 176, 177 de prueba, 16


Object, 46 definición, 10, 161
Random, 141, 144 derivada, 6
RuntimeException, 73, 173 hija, 6
Scanner, 166 importación, 160
String, 18 jerarquı́a, 7, 10
System, 162, 166 padre, 6, 89
args, 162, 171 principal, 165
argumentos, 18 subclase, 6, 174
arreglo super clase, 8, 174
args, 172 cohesión, 9, 34
atributos, 5, 7, 19 cola de espera, 79
autorreferencia, 48 peek, 97
de prioridad, 86
C, 41, 160, 164, 166, 176 fin, 79
C++, 13, 41, 160 inicio, 79
C#, 41 operaciones primitivas, 80
cadenas representación, 81
concatenación, 167 colección, 175, 177
cláusula comportamiento, 10, 17
class, 161 constructor, 21, 44, 161
extends, 29 base, 45
implements, 91 sobrecarga, 46
import, 160 conversión forzada, 175
interface, 89
new, 17, 21, 161, 170 do-while, 169
super, 29, 60
this, 45, 46 Eiffel, 13
throws, 59 encapsulamiento, 11, 34
throw, 44 enfoque estructurado, 2, 4, 5, 8
try-catch-finally, 62, 74, 85 entidades, 4, 5
package, 160 envoltura, 166
clase, 6, 10 especificador de formato, 164, 176
Object, 46, 167 excepción, 173
abstracta, 7 RuntimeException, 174
atributos, 19 ExcepcionEDVacia, 59, 60, 62, 85,
autorreferida, 48 146
base, 8, 174 lanzar una (throw ), 44
ÍNDICE ANALÍTICO 183

FIFO, 79 ordenada, 104


for, 169 operaciones primitivas, 104
funciones, 4 representación, 105
simplemente enlazadas, 117
genéricos (generics), 63, 174
GUI, 13, 166 máquina virtual, 165, 176
métodos, 4, 7, 16
herencia, 8, 10, 11, 25, 89
get, 20, 33, 36
extends, 29
nextInt, 166, 177
abstracción, 25, 110
parseInt, 177
múltiple, 89
printf, 164
IDE, 164 println, 162
identificador, 160 print, 163
if, 167 set, 19, 31, 33, 36
if-else, 167 toString, 46, 167
instancia, 6, 7, 10, 16, 17 argumentos, 18
interfaz, 5, 19 definición, 162
gráfica, 166 firma de, 162
lanzar excepciones (throws), 59
Java, 15, 21, 33, 41, 160 llamado explı́cito, 167
recolector de basura, 60 llamado implı́cito, 168
java, 165 privado, 75, 77
javac, 164 sobre escritura (override), 31
jdk, 164, 174 módulo, 8, 9
jerarquı́a, 11 main, 17, 162, 165
Exception, 174 mensajes, 4, 5, 16
de clases, 7 modularidad, 8, 10
JVM, 160, 165, 174
java, 165 número racional, 43
recolector de basura, 60 niveles de acceso, 26, 33, 161
Kuhn Thomas, 3 público (public), 19, 161
privado (private), 19, 161
LIFO, 55 protegido (protected ), 83, 161
lista, 103 nodo, 48
circular, 118 notación
doblemente enlazada, 120 interfija, 69
primitivas, 120 postfija, 69
enlazada, 103 prefija, 69
184 ÍNDICE ANALÍTICO

null, 21, 36, 49 pila, 55, 110


peek, 56, 74
objetos, 4, 5, 33 pop, 56, 110
acciones, 10 push, 56, 110
clase, 4, 10 primitiva, 57
comportamiento, 10, 17 primitivas, 56
construcción, 17, 21, 161 tope, 55
creación, 17, 21, 161 polimorfismo, 11, 21, 23, 31
definición, 17 POO, 1, 9, 42
identificador, 5 caracterı́sticas fundamentales, 9
instancia, 10 privado, 19, 161
interfaz, 19 procedimientos, 4
memoria, 10 programación
mensajes, 4, 10 entorno de, 164
personalidad, 33 estructurada, 5, 8, 162
protocolo, 6 orientada a objetos, 9, 13, 42
responsabilidades, 4, 17 nociones, 50
rol, 4 protegido, 161
servicio, 4
solicitud, 10 referencia, 40, 48, 49, 64
ocultación relación
principio de, 11, 19, 33 de asociación, 73
OO, 1, 5, 6, 12 de composición, 73, 110
operadores de orden, 88
aritméticos, 176 es un (is-a), 8, 27
punto, 35, 46 tiene (has-a), 8
relacionales, 168 responsabilidades, 4, 17
Oracle, 160 rotación
orientado a objetos, 1, 33, 42 derecha, 148
paradigma, 2, 4, 12, 33 derecha izquierda, 152
programación, 1, 42 doble, 150
izquierda, 149
público, 19, 161 izquierda derecha, 150
paquete, 160 simple, 148
paradigma, 2–4
de programación, 3 servicios, 4
definición, 2, 3 clases de, 162
orientado a objetos, 4 Simula, 1
ÍNDICE ANALÍTICO 185

Smalltalk, 1, 13
sobre escribe, 7, 27, 31
sobrecarga, 21, 23
operadores, 33
software
crisis, 1
portabilidad, 160
subclase, 6, 174
Sun Microsystems, 160
super clase, 8, 174

Thomas Kuhn, 3
tipo de dato, 40, 41, 166
double, 52, 177
float, 52, 177
int, 52, 166, 169, 170
abstracto, 41
booleano, 167
genéricos, 175
primitivo, 166, 169, 176, 177

UML, 25, 73, 81, 105, 174

while, 169
WORA, 160

Xerox, 1, 13
186 ÍNDICE ANALÍTICO
Agradecimientos

Antes y primero que nada y nadie, quiero agradecer a mis hijos Ricardo
y Bruno, quienes al momento de escribir esto cuentan con seis y cinco años
respectivamente, ya que con sus ocurrencias, sus acciones, su inocencia y su
motivación implı́cita, hacen que la vida y mi esfuerzo valgan la pena.
Quiero renovar también el agradecimiento a mis padres Marı́a Luisa
Rodrı́guez y Rodolfo Ruiz por su educación y formación. Especialmente quie-
ro agradecer a mi madre por apoyarme siempre e incondicionalmente cuando
más lo he necesitado, sobre todo en un trance especial de mi vida; siempre
ha sido, es, y será, la mujer que más ame y añore...
Aprovecho también para agradecer especialmente a Thelma su invaluable
ayuda para la realización de imágenes, el diseño de portada, la revisión del
texto, trámites, y mil cosas más; pero sobre todo por estar conmigo en los
momentos cruciales que ella conoce.
Agradezco el apoyo y la confianza de mi hermano Rodolfo, quien jugó un
papel fundamental para que yo pudiera realizar mis estudios de posgrado.
No sólo tienes mi agradecimiento, reconocimiento y admiración, sino también
una profunda estimación y aprecio.
Para mis amigos Tomás Balderas Contreras y Ricardo Pérez-Aguila, mi
reconocimiento profesional y mi agradecimiento por sus comentarios y suge-
rencias.
Finalmente, agradezco también a todos los estudiantes que me ayudaron
implı́citamente con sus comentarios, preguntas, dudas y observaciones. Este
libro es, sin duda alguna, resultado también de sus aportaciones.

187
188 AGRADECIMIENTOS
Acerca del Autor

Ricardo Ruiz Rodrı́guez nació en la


ciudad de Puebla, Pue., México. Ac-
tualmente y desde el año 2002, es pro-
fesor investigador adscrito al Instituto
de Computación en la Universidad Tec-
nológica de la Mixteca (UTM), en Hua-
juapan de León, Oaxaca, México, y cuen-
ta con más de 15 años de experiencia co-
mo docente.
El maestro Ricardo además de la do-
cencia, se ha desempeñado también co-
mo desarrollador de software en la indus-
tria, y se certificó como PSP Developer
en 2013, mismo año en el que publicó su
primer libro ”Una Introducción a la Pro-
gramación Estructurada en C ”.
Entre sus intereses actuales se encuentran los métodos de enseñanza de
las ciencias de la computación, la música, y la música por computadora, pero
se ha desarrollado académicamente en áreas como la Ingenierı́a de Software,
la Interacción Humano-Computadora, y el Paradigma Orientado a Objetos.
El autor tiene la Licenciatura en Ciencias de la Computación por la Be-
nemérita Universidad Autónoma de Puebla (BUAP) y la Maestrı́a en Cien-
cias con especialidad en Ingenierı́a en Sistemas Computacionales, por la Uni-
versidad de las Américas-Puebla (UDLA-P).
Información adicional del autor, ası́ como el material relacionado con este
libro y el anterior, se pueden consultar en su página personal:

http://sites.google.com/site/ricardoruizrodriguez/

189

También podría gustarte