Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Objetos
Una Aplicación a las Estructuras de Datos en
Java TM
Índice de figuras ix
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
v
ÍNDICE GENERAL
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
A. Java 159
A.1. Orı́genes y caracterı́sticas . . . . . . . . . . . . . . . . . . . . . 160
A.2. Estructura general de una clase . . . . . . . . . . . . . . . . . 160
vii
ÍNDICE GENERAL
Bibliografı́a 179
Agradecimientos 187
viii
Índice de figuras
ix
ÍNDICE DE FIGURAS
x
ÍNDICE DE FIGURAS
xi
Índice de ejemplos
xiii
ÍNDICE DE EJEMPLOS
xiv
ÍNDICE DE EJEMPLOS
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
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.
xix
PREFACIO
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.
xx
I consider to paradigms as universally recognized scientific
achievements that, for a time, provide model problems and
solutions for a community of researchers.
xxi
Capı́tulo 1
Orientación a Objetos
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
Creatividad.
Inteligencia.
Lógica.
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é:
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.
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.
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. Cohesión.
2. Acoplamiento.
1. Todo es un objeto.
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.
Cuando un objeto hereda de más de una clase se dice que hay herencia
múltiple.
1.6. Ejercicios
1. Investigue más acerca de la historia y desarrollo de la programación
orientada a objetos.
Programación Orientada a
Objetos
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
15
16 CAPÍTULO 2. PROGRAMACIÓN ORIENTADA A OBJETOS
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 }
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:
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 }
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
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
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 }
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
1. Identificador de la clase.
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
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 }
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 }
objeto.mensaje(lista de argumentos)
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
nombre (String).
apellido1 (String).
apellido2 (String).
En base a lo anterior:
Estructuras de datos
Ask not what you can do to your data structures, but rather ask
what your data structures can do for you.
39
40 CAPÍTULO 3. ESTRUCTURAS DE DATOS
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.
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
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 }
p * r.obtenDenominador()
es equivalente a la expresión:
this.p * r.obtenDenominador()
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.
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.
por:
c1 = (a + bi) y c2 = (c + di)
donde a, b, c, d ∈ R.
La suma de c1 y c2 se define como:
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
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
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.
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
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.
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 }
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 }
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 }
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 }
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
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
3
Observe la lı́nea 6 del Ejemplo 4.7.
4.2. IMPLEMENTACIÓN 65
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.
{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:
valida = true;
p = pila vacı́a;
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.");
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.
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:
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.
Aspectos a considerar
Finalmente, respecto a las notaciones prefija y postfija cabe hacer mención
de un par de consideraciones:
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.
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)
a) AB+
b) AB + C−
c) AB + CD − ×
d ) AB@C × D − EF/GH + /+
e) AB + C × DE − −F G + @
f ) ABCDE@ × /−
a) +AB
b) − + ABC
c) × + AB − CD
d ) + − ×@ABCD//EF + GH
e) @ − × + ABC − DE + F G
f ) −A/B × C@DE
Colas de espera
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.
79
80 CAPÍTULO 5. COLAS 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.
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
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 }
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
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 }
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 }
Figura 5.5: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método elimina
Figura 5.6: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método inserta
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:
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
2. Cero (0) si el objeto que recibe el mensaje, es igual al objeto que recibe
como argumento.
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 }
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.8: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método elimina
Figura 5.9: Diagrama de clases en UML para una cola de prioridad ascendente
con la redefinición del método inserta
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.
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
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:
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
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
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
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
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:
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.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
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
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.
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
Figura 6.7: Abstracción de una lista enlazada circular como una secuencia de
nodos
(a) Simple
(b) Circular
Figura 6.9: Diagrama de clases UML para una lista doblemente enlazada
122 CAPÍTULO 6. LISTAS ENLAZADAS
Bancos.
Lı́neas aéreas.
Terminal de autobuses.
Un largo etcétera.
6.7. Ejercicios
1. Considerando la definición de una lista enlazada, modifique el Ejemplo
6.1 para que:
Float.
String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).
Float.
String.
Persona (del Ejemplo 2.11 del Capı́tulo 2).
Cientifico (del Ejemplo 2.12 del Capı́tulo 2).
Promedio n
i=1 xi
xprom = (6.1)
n
Desviación estándar
n
− xprom )2
i=1 (xi
σ= (6.2)
n−1
Regresión lineal
n
( x y ) − (nxprom yprom )
β1 = n i i 2
i=1
(6.3)
( i=1 xi ) − (nx2prom )
Árboles binarios
7.1. Definición
Un árbol binario es un conjunto finito de elementos, el cual está vacı́o,
o dividido en tres subconjuntos disjuntos:
129
130 CAPÍTULO 7. ÁRBOLES BINARIOS
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. Visitar la raı́z.
2. Visitar la raı́z.
3. Visitar la raı́z.
1. Visitar la raı́z.
Por otro lado, para recorrer un árbol binario no vacı́o en orden inverso,
se ejecutan tres operaciones:
2. Visitar la raı́z.
3. Visitar la raı́z.
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
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.
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
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 }
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
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 }
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:
Figura 7.4: Diagrama de clases UML para un árbol binario de búsqueda con
eliminación
Ejemplo
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
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
Árbol AA.
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
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
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).
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
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
1
Java es una marca registrada de Oracle Corporation.
2
http://docs.oracle.com/javase/7/docs/api/
159
160 APÉNDICE A. JAVA
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 }
1. public (público) hace que una clase, método o atributo sea accesible
por cualquier otra clase.
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 }
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
$ javac Bienvenido1.java
$ java Bienvenido1
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 }
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
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 }
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
Operador Descripción
== Igual que
!= Distinto de
< Menor estricto que
> Mayor estricto que
<= Menor igual que
>= Mayor igual que
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 }
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 }
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 }
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:
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 }
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 }
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
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
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:
Operador Descripción
/ División (cociente)
% Módulo (residuo)
* Multiplicación
+ Adición
- Sustracción
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?
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 }
[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.
179
180 BIBLIOGRAFÍA
Índice Analı́tico
181
182 ÍNDICE ANALÍTICO
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
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
http://sites.google.com/site/ricardoruizrodriguez/
189