Академический Документы
Профессиональный Документы
Культура Документы
• SEMANA 1
Tema
(s):
NÚCLEO
TEMÁTICO:
REPASO
Y
MOTIVACIÓN
DE
LOS
TEMAS
DEL
MÓDULO
JAVA
COMO
LENGUAJE
DE
PROGRAMACIÓN
ECLIPSE
COMO
ENTORNO
DE
PROGRAMACIÓN
CONCEPTOS
SOBRE
PROGRAMACIÓN
ORIENTADA
A
OBJETOS
1-‐ Identificar
variables
y/o
parámetros
relevantes
en
la
dinámica
del
sistema,
y
así
mismo
descartar
aspectos
irrelevantes,
o
de
poca
incidencia
con
el
fin
de
llegar
a
modelos
matemáticos
que
permitan
soluciones
analíticas.
1
[ POLITÉCNICO GRANCOLOMBIANO ]
6-‐ Aprender
autónomamente
el
dominio
de
las
herramientas
tecnológicas
actuales
para
la
implementación
de
soluciones
de
sistemas,
esto
incluye
lenguajes
de
programación,
ambientes
de
programación,
metodologías,
paradigmas
de
desarrollo,
librerías,
frameworks,
etc.
7-‐ Servirse de la autodisciplina para la ejecución de tareas de manera efectiva.
Lectura
1-‐1
-‐
Uno
Leer
y
comprender
los
conceptos
expuestos
en
las
lecturas.
Java
como
lenguaje
de
programación.
Lectura
1-‐2
-‐
Uno
Leer
y
comprender
los
conceptos
expuestos
en
las
lecturas.
Eclipse
como
entorno
de
programación.
Lectura
1-‐3
-‐
Uno
Leer
y
comprender
los
conceptos
expuestos
en
las
lecturas.
Conceptos
sobre
programación
orientada
a
objetos
OVA -‐Introducción a Java. Uno Comprender los conceptos expuestos en el OVA.
Video
diapositivas
Uno
Uno
Leer
y
comprender
los
conceptos
expuestos
en
los
videos
Programación
orientada
a
diapositivas.
objetos.
Ejercicios
propuestos.
Uno
Desarrollar
de
forma
individual
los
ejercicios
propuestos
para
la
semana.
Proyecto de aula. Uno Revisar el instructivo e iniciar el proyecto.
Recursos
adicionales.
Uno
Revisar
cuidadosamente
el
material
en
Java
organizado
como
proyectos
en
Eclipse,
con
el
fin
de
afianzar
la
práctica.
[
ESTRUCTURAS
DE
DATOS
]
2
• SEMANA 2
Tema
(s):
Lectura
2-‐1
-‐
Dos
Leer
y
comprender
los
conceptos
expuestos
en
las
Recursión.
lecturas.
Lectura
2-‐2
-‐
Dos
Leer
y
comprender
los
conceptos
expuestos
en
las
Análisis
de
Algoritmos.
lecturas.
Video
diapositivas
1
-‐
Torres
Dos
Leer
y
comprender
los
conceptos
expuestos
en
los
de
Hanoi.
videos
diapositivas.
Ejercicios
propuestos.
Dos
Desarrollar
de
forma
individual
algunos
de
los
ejercicios
propuestos
para
la
semana.
Video resumen. Dos Ver el resumen de la unidad uno.
3
[ POLITÉCNICO GRANCOLOMBIANO ]
ESTRUCTURAS DE DATOS
UNIDAD UNO - SEMANA UNO
JAVA COMO LENGUAJE DE PROGRAMACIÓN *
TABLA DE CONTENIDO
1. ¿QUÉ ES JAVA? 2
2. ¿CÓMO DESCARGO JAVA? 3
3. ¿CÓMO INSTALO JAVA? 5
3.1. INSTALACIÓN DE JAVA EN LINUX 5
3.2. INSTALACIÓN DE JAVA EN WINDOWS 6
4. ¿CÓMO ESCRIBO UN PROGRAMA EN JAVA? 7
5. ¿CÓMO COMPILO UN PROGRAMA EN JAVA? 9
6. ¿CÓMO EJECUTO UN PROGRAMA EN JAVA? 10
7. ¿DÓNDE ENCUENTRO DOCUMENTACIÓN SOBRE JAVA? 11
EN RESUMEN 11
PARA TENER EN CUENTA 11
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. ¿QUÉ ES JAVA?
Todos los programas de Java deben ser codificados en archivos con extensión .java que
luego son compilados para generar archivos con extensión .class que se pueden ejecutar en
la Máquina Virtual de Java (conocida en inglés como la Java Virtual Machine (JVM)). La
Máquina Virtual de Java está disponible para los sistemas operativos Linux, Solaris y
Windows.
†
La sintaxis se refiere a las reglas que indican cómo escribir programas en el lenguaje.
‡
Imágenes tomadas de http://www.jfree.org/jfreechart/samples.html. JFreeChart Samples, © 2005-2009
Object Refinery Limited (recuperado en noviembre de 2010).
ESTRUCTURAS DE DATOS 2
Una vez compilado un programa en Java, el .class generado puede ejecutarse en cualquier
máquina que tenga instalada la JVM, sin importar que tenga como sistema operativo Solaris,
Linux o Windows. Por esta razón se dice que los programas en Java son portables, ya que se
pueden ejecutar en cualquier computador que tenga la Máquina Virtual de Java, sin tener
que modificar el código fuente.
Para poder programar aplicaciones en Java se debe instalar la última versión del Kit de
Desarrollo de Java (Java Development Kit (JDK)) en su versión estándar (Java Standard Edition
(Java SE)) §.
Haga clic en Downloads, a continuación clic en Java SE, y finalmente clic en Download JDK
para poder iniciar el proceso de descarga.
§
Visite la página http://java.sun.com/ con su explorador favorito (Firefox o Internet Explorer por ejemplo).
ESTRUCTURAS DE DATOS 3
Gráfica 6: Paso 2 de la descarga del JDK.
ESTRUCTURAS DE DATOS 4
Gráfica 9: Paso 5 de la descarga del JDK.
En caso de que la descarga no inicie automáticamente, debe hacer clic en el enlace que
referencia al instalador.
La instalación del Kit de Desarrollo de Java (JDK) depende de su sistema operativo. Una vez
complete el proceso de instalación, tendrá en su computador:
1. El Kit de Desarrollo de Java (Java Development Kit (JDK)), que nos permite compilar
programas escritos en Java.
ESTRUCTURAS DE DATOS 5
Gráfica 11: Paquetes relacionados con Java vistos en Synaptic en la distribución Ubuntu 9.10 (Karmic Koala) de Linux.
A partir de la distribución 10.04 LTS de Ubuntu (Lucid Lynx), Java se provee en el paquete
openjdk-6-jdk, que es la versión libre para la plataforma Java.
Una vez descargado de Internet el instalador de Java, sólo resta ejecutarlo. Primero, dé doble
clic al archivo descargado en la sección 1.2. ¿Cómo descargo Java?.
ESTRUCTURAS DE DATOS 6
Gráfica 13: Siguientes pasos de la instalación del JDK (en Windows).
ESTRUCTURAS DE DATOS 7
public class declara una clase **, que contendrá el comportamiento que
Ejemplo1
queremos que tenga nuestro programa. Dentro de la clase Ejemplo1 se coloca public static
void main(String[] args) para definir el método main del programa, que es el punto de
inicio de la ejecución (es decir, por donde va a comenzar a correr el programa). Y finalmente,
dentro del método main se pone la instrucción System.out.println("Hola Mundo"), que
imprime en la consola del sistema una línea de texto con la frase Hola mundo.
Sólo resta guardar nuestro programa. Hacemos clic en Archivo, luego en Guardar Como, y
escogemos el directorio donde queremos almacenar el archivo.
Gráfica 15: Guardar código fuente Java en un archivo con extensión .java.
Es importante que:
Como tipo de archivo se seleccione Todos los archivos en vez de Documentos de texto
(*.txt).
Como nombre de archivo se coloque el nombre de la clase seguido de la extensión .java (en
nuestro ejemplo en particular, el archivo debe llamarse Ejemplo1.java porque la clase se
llama Ejemplo1).
Como codificación de archivo se escoja ANSI.
Más adelante introduciremos Eclipse, que es un entorno de programación que nos facilita el
desarrollo de proyectos de programación sobre Java.
**
En la programación orientada a objetos, una clase representa un concepto del mundo real.
ESTRUCTURAS DE DATOS 8
5. ¿CÓMO COMPILO UN PROGRAMA EN JAVA?
Gráfica 16: Búsqueda de la consola de comandos (símbolo del sistema) en el menú de Accesorios de Windows.
Gráfica 17: Ejemplo de ubicación de la consola de comandos sobre un directorio, con el comando cd.
ESTRUCTURAS DE DATOS 9
En caso de que la consola de comandos no reconozca javac después de haber instalado Java,
en la opción Propiedades del sistema (teclas Windows+Pausa) > Opciones avanzadas >
Variables de entorno, busque la variable Path y añádale un punto y coma seguido del
directorio bin donde se encuentra instalado Java (por ejemplo, C:\Archivos de
programa\Java\jdk1.6.0_18\bin). El Path debería lucir así:
...;C:\Archivos de programa\Java\jdk1.6.0_18\bin
Para correr el método main de la clase Ejemplo1, sobre la misma consola de comandos donde
compiló el programa, ejecute la instrucción java –cp . Ejemplo1.
ESTRUCTURAS DE DATOS 10
El comando java es el responsable de invocar a la Máquina Virtual de Java para que ejecute
el programa recién compilado. Observe que después de colocar java –cp . Ejemplo1 en la
consola de comandos, nuestro programa ejecutó e imprimió exitosamente en la consola la
frase Hola Mundo.
Documentación del API de la versión 6 del Kit de Desarrollo de Java (conocido como Java
Development Kit (JDK))
Java™ Platform, Standard Edition 6 API Specification
http://java.sun.com/javase/6/docs/api/
EN RESUMEN
Java es un lenguaje de programación orientado a objetos creado por Sun Microsystems en los años
noventa y que hoy en día es usado ampliamente tanto en entornos académicos como empresariales.
El instalador de Java se encuentra en la página WEB http://java.sun.com/.
El Hola Mundo en Java se escribe de la siguiente manera:
public class Ejemplo1 {
public static void main(String[] args) {
System.out.println("Hola Mundo");
}
}
Para compilar un programa escrito en Java, ejecute en una consola de comandos la instrucción
javac NombreClase.java
donde NombreClase debe reemplazarse por el nombre de la clase que tiene el método main.
Para ejecutar un programa compilado en Java, ejecute en una consola de comandos la instrucción
java –cp . NombreClase
donde NombreClase debe reemplazarse por el nombre de la clase que tiene el método main.
ESTRUCTURAS DE DATOS 11
¿Qué sucede si el nombre del archivo no coincide con el nombre de la clase? Haga un experimento
donde defina una clase Ejemplo3 que imprima Hola Mundo pero que no quede guardada en el
archivo Ejemplo3.java sino en el archivo Ejemplo.java. ¿Qué error se lanza cuando se compila
Ejemplo.java?
Investigue cómo se le puede pedir datos al usuario por consola, y desarrolle un programa que pida
dos números enteros al usuario e imprima en la consola la suma de éstos.
ESTRUCTURAS DE DATOS 12
ESTRUCTURAS DE DATOS
UNIDAD UNO - SEMANA UNO
ECLIPSE COMO ENTORNO DE PROGRAMACIÓN *
TABLA DE CONTENIDO
1. ¿QUÉ ES ECLIPSE? 2
2. ¿CÓMO DESCARGO ECLIPSE? 2
3. ¿CÓMO INSTALO ECLIPSE? 3
4. ¿CÓMO INICIO ECLIPSE? 3
5. ¿CÓMO CONSTRUYO UN PROYECTO EN ECLIPSE? 5
6. ¿CÓMO CONSTRUYO EL PROGRAMA HOLA MUNDO EN ECLIPSE? 7
7. ¿CÓMO EJECUTO EL PROGRAMA HOLA MUNDO EN ECLIPSE? 9
EN RESUMEN 10
PARA TENER EN CUENTA 10
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. ¿QUÉ ES ECLIPSE?
Para instalar la última versión de Eclipse para programar sobre Java, visite la página
http://www.eclipse.org/ con su explorador favorito (Firefox o Internet Explorer por ejemplo).
Haga clic en Downloads y luego en Eclipse Classic. Finalmente, escoja el lugar de donde
quiere efectuar la descarga y continúe.
ESTRUCTURAS DE DATOS 2
3. ¿CÓMO INSTALO ECLIPSE?
Instalar Eclipse es tan fácil como descomprimir el archivo empaquetado .zip que descargó de
la página WEB, usando alguna herramienta de compresión como WinZip o WinRar.
ESTRUCTURAS DE DATOS 3
Gráfica 7: Pantalla de bienvenida de Eclipse, mientras se efectúa su carga.
La primera vez que se carga Eclipse va a pedir que configuremos el directorio de trabajo
donde se van a guardar todos los proyectos que se realicen.
En el menú Window bajo la opción Show View se pueden activar vistas como las siguientes:
Vista Navegador (Navigator): nos muestra todos los proyectos disponibles y los directorios
que conforman el proyecto que se está trabajando.
Vista Problemas (Problems): nos muestra los errores de compilación.
Vista Búsquedas (Search): nos muestra los resultados de las búsquedas que se realicen.
ESTRUCTURAS DE DATOS 4
Vista Consola (Console): nos muestra la consola del sistema, donde se puede pedir
información al usuario e imprimir resultados.
Para crear un proyecto en Eclipse se hace clic derecho en la vista Navigator, se posa el
puntero del ratón sobre New y se hace clic en Java Project.
Luego se suministra el nombre del proyecto, se diligencian los demás datos de ser necesario,
y se hace clic en el botón Finish.
ESTRUCTURAS DE DATOS 5
Gráfica 12: Configuración de un nuevo proyecto en Eclipse.
El proyecto creado se muestra en la vista Navigator. El directorio src contendrá los archivos
.java con el código fuente, y el directorio bin contendrá los archivos .class compilados.
ESTRUCTURAS DE DATOS 6
6. ¿CÓMO CONSTRUYO EL PROGRAMA HOLA MUNDO EN
ECLIPSE?
Para crear una clase sobre el proyecto se hace clic derecho en el nombre del proyecto
(Prueba en nuestro ejemplo), se posa el puntero del ratón sobre New y se hace clic en Class.
Luego, se suministra el nombre de la clase, se diligencian los demás datos de ser necesario, y
se hace clic en el botón Finish.
ESTRUCTURAS DE DATOS 7
Gráfica 15: Configuración de una nueva clase en Eclipse.
Eclipse automáticamente crea el archivo .java y declara la clase con la sentencia public
class NombreClase, donde NombreClase es el nombre que le dimos a la clase (Prueba en
nuestro ejemplo).
Para construir el método main ubicamos el cursor dentro de los corchetes, y lo declaramos.
Luego, añadimos la instrucción System.out.println("Hola Mundo") para imprimir la frase
Hola Mundo en la consola. No hay que olvidar guardar el programa con la opción Save del
menú File.
ESTRUCTURAS DE DATOS 8
Gráfica 17: Hola Mundo en Eclipse.
La primera vez que se ejecuta un programa debe hacerse clic en el menú Run, luego en Run
As, y finalmente en Java Application.
Se puede observar en la vista Console que el programa imprimió la frase Hola Mundo.
La próxima vez que se necesite volver a ejecutar el programa, es suficiente hacer clic en Run.
ESTRUCTURAS DE DATOS 9
EN RESUMEN
Eclipse es un entorno de programación utilizado para desarrollar proyectos en lenguajes como Java.
Ofrece gran cantidad de opciones y facilidades que hacen de la programación una actividad más
cómoda y sencilla.
ESTRUCTURAS DE DATOS 10
ESTRUCTURAS DE DATOS
UNIDAD UNO - SEMANA UNO
CONCEPTOS SOBRE PROGRAMACIÓN
ORIENTADA A OBJETOS *
TABLA DE CONTENIDO
1. CLASES 2
2. ATRIBUTOS Y MÉTODOS 3
3. OBJETOS 5
4. ARREGLOS 6
EN RESUMEN 7
PARA TENER EN CUENTA 7
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. CLASES
Todos los animales mostrados se reúnen bajo el mismo concepto: perro, y aunque los cuatro
perros son distintos, tienen muchas cosas en común, pues todos son perros.
†
Imágenes tomadas de http://www.morguefile.com/. morgueFile: public image archive for creatives by
creatives, © 2009-2010 MORGUEFILE.
ESTRUCTURAS DE DATOS 2
Observe que las características consideradas dependen de qué nos importa. Así pues, la
abstracción es el proceso que seguimos para escoger las características que nos interesan
sobre los perros con el objetivo de tener una idea en la mente de qué es un perro.
Obviamente los perros tienen muchas más propiedades que pueden no interesarnos en
nuestra abstracción: número de pulgas, color favorito, número de horas promedio que
duerme, secuencia de ADN, etc. Por esto es importante el proceso de abstraer: nos permite
quedarnos con lo que realmente necesitamos.
Entonces, la clase Perro corresponde con la abstracción mental de todos los objetos del
mundo que nosotros identificamos como perros. Para definir la clase Perro en Java se debe
escribir:
2. ATRIBUTOS Y MÉTODOS
Recurso como proyecto en Eclipse: Clases.zip.
// -------------------------
// - Atributos de la clase -
// -------------------------
// ----------------------------------
// - Método constructor de la clase -
// ----------------------------------
ESTRUCTURAS DE DATOS 3
// --------------------------------------
// - Atributos analizadores de la clase -
// --------------------------------------
// ---------------------------------------
// - Atributos modificadores de la clase -
// ---------------------------------------
Es importante resaltar que los atributos representan características y que los métodos
representan comportamiento. Según su función, los métodos se clasifican en:
ESTRUCTURAS DE DATOS 4
3. OBJETOS
Recurso como proyecto en Eclipse: Clases.zip.
Un objeto es una instancia o particularización de una clase. Por ejemplo, cuatro objetos de la
clase Perro son:
‡
Gráfica 4: Cuatro objetos distintos de la clase Perro .
Hay tantos objetos de la clase Perro como perros en este mundo. Para crear en Java una
instancia de una clase hay que invocar un método constructor a través de la palabra clave
new.
Código 5: Ejemplo que ilustra la creación de instancias en Java, que crea cinco perros.
Perro perro1=new Perro("Firulais","Chihuahua","17/02/2007",true);
Perro perro2=new Perro("Paca","Dálmata","31/12/2009",false);
Perro perro3=new Perro("Fito","Pastor Alemán","07/08/2008",true);
Perro perro4=new Perro("Sally","French Poodle","07/08/2008",false);
Perro perro5=new Perro("Fito","French Poodle","28/02/2009",true);
Las propiedades de cada objeto son definidas mediante los valores que toman sus atributos.
Por ejemplo:
El objeto perro1 tiene nombre "Firulais", raza "Chihuahua", fecha de nacimiento
"17/02/2007", y es de género masculino.
El objeto perro2 tiene nombre "Paca", raza "Dálmata", fecha de nacimiento "31/12/2009", y
es de género femenino.
El objeto perro3 tiene nombre "Fito", raza "Pastor Alemán", fecha de nacimiento
"07/08/2008", y es de género masculino.
El objeto perro4 tiene nombre "Sally", raza "French Poodle", fecha de nacimiento
"07/08/2008" y es de género femenino.
‡
Imágenes tomadas de http://www.morguefile.com/. morgueFile: public image archive for creatives by
creatives, © 2009-2010 MORGUEFILE.
ESTRUCTURAS DE DATOS 5
El objeto perro5 tiene nombre "Fito", raza "French Poodle", fecha de nacimiento
"28/02/2009", y es de género masculino.
Aunque todos los objetos de la clase Perro tienen nombre, raza, fecha de nacimiento y
género, dos perros distintos podrían tener distinto nombre, distinta raza, distinta fecha de
nacimiento y distinto género. En particular, perro1 tiene nombre "Firulais", y perro3 tiene un
nombre distinto ("Fito"), pero ambos son perros de género masculino.
4. ARREGLOS
Recurso como proyecto en Eclipse: Clases.zip.
Un arreglo es una secuencia de elementos del mismo tipo, que son almacenados de forma
contigua.
0 1 2 3 4 5 6 7
M A N Z A N A
0 1 2 3 4 5 6
El acceso a los elementos de un arreglo se realiza mediante índices, que van desde 0 para la
primera posición del arreglo hasta el tamaño menos uno para la última posición del arreglo.
Por ejemplo, considere el arreglo M, A, N, Z, A, N, A. El tamaño del arreglo es siete, el elemento
de la posición 0 es una M, el elemento de la posición 1 es una A, el elemento de la posición 2
es una N, …, y el elemento de la posición 6 es una A. Observe que la primera posición del
arreglo es la 0, y que la última posición del arreglo es la 6, que coincide con el tamaño del
arreglo menos uno.
ESTRUCTURAS DE DATOS 6
}
}
EN RESUMEN
Abstracción es el proceso mediante el cual se representa en el mundo de las ideas un conjunto de
objetos, seleccionando sólo aquellas características que interesan, excluyendo toda propiedad que no
importe.
Una clase es la abstracción de un conjunto de objetos reunidos bajo el mismo concepto. Una clase se
define mediante 1. un nombre que la identifica, 2. atributos que describen las características de los
objetos de la clase, y 3. métodos que describen el comportamiento de los objetos de la clase.
Según su función, los métodos se clasifican en:
Métodos constructores: son los responsables de crear nuevas instancias de la clase.
Métodos analizadores: son los responsables de permitir consultas sobre los atributos de la clase.
Métodos modificadores: son los responsables de permitir modificaciones sobre los atributos de la
clase.
Un objeto es una instancia o particularización de una clase.
Un arreglo es una secuencia de elementos del mismo tipo, que son almacenados de forma contigua.
ESTRUCTURAS DE DATOS 7
ESTRUCTURAS DE DATOS
UNIDAD UNO - SEMANA UNO
EJERCICIOS PROPUESTOS *
Desarrolle una aplicación en Java que, dado un arreglo de números flotantes, imprima en
consola el promedio de los valores del arreglo. Tanto el número de elementos del arreglo
como los elementos del arreglo deben ser pedidos al usuario a través de la consola del
sistema.
2. CONCATENACIÓN DE ARREGLOS
3. REVERSO DE UN ARREGLO
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
ESTRUCTURAS DE DATOS
UNIDAD UNO - SEMANA DOS
RECURSIÓN *
TABLA DE CONTENIDO
1. FUNCIONES RECURSIVAS 2
1.1. FUNCIÓN DE FIBONACCI 3
2. MEMOIZATION 9
2.1. IMPLEMENTACIÓN DE LA FUNCIÓN DE FIBONACCI USANDO MEMOIZATION 9
3. DIVIDIR Y CONQUISTAR (DIVIDIR Y VENCER) 10
3.1. TORRES DE HANOI 10
3.2. BÚSQUEDA BINARIA (BINARY SEARCH) 15
3.3. ALGORITMO DE ORDENAMIENTO POR MEZCLA (MERGE SORT) 18
4. BACKTRACKING 19
EN RESUMEN 20
PARA TENER EN CUENTA 20
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. FUNCIONES RECURSIVAS
Una función es recursiva si está definida en términos de sí misma. Claramente debe haber
casos especiales en los que una función recursiva esté definida mediante constantes y
llamados a otras funciones, porque de lo contrario existirían llamados recursivos infinitos a la
función. Por ejemplo, la función
no está bien definida porque la evaluación en cualquier punto implicaría un número infinito
de llamados, lo que se evidencia al intentar calcular la función en algún , por decir algo, en
:
aplicando la definición con .
aplicando la definición con .
aplicando la definición con .
aplicando la definición con .
aplicando la definición con .
...
Ventajas:
• Frecuentemente, al trabajar sobre estructuras de datos definidas recursivamente es más
natural diseñar algoritmos recursivos que iterativos. La forma de las soluciones recursivas
sobre estructuras de datos definidas recursivamente reflejan el carácter recursivo de la
definición.
• A veces es más fácil pensar una solución recursiva que una iterativa.
• Para algunos problemas, se puede diseñar algoritmos recursivos eficientes más sencillos de
escribir que sus contrapartes iterativas.
Desventajas:
• La máquina debe manejar estructuras adicionales para controlar los llamados recursivos, lo
que resulta en mayor tiempo de ejecución y espacio extra adicional.
• Hay un límite para el nivel de anidamiento de los llamados recursivos que, si se sobrepasa,
se lanza una excepción en la máquina (StackOverflow).
ESTRUCTURAS DE DATOS 2
• Si los casos inductivos tienen más de una referencia a la función es posible que se efectúen
reiteradamente llamados a la función con los mismos parámetros, lo que implica gastar
tiempo de ejecución en repetir cálculos innecesarios. Para resolver este problema se puede
aplicar la técnica conocida como Memoization, que consiste en declarar una estructura de
datos adicional que guarde los resultados de los llamados recursivos de tal forma que no se
repitan cálculos innecesarios.
que puede ser evaluada sobre cualquier número natural , simulando los llamados
recursivos:
ESTRUCTURAS DE DATOS 3
return 1; // Retornar 1
}
else { // Si n es mayor o igual que dos
return fibA(n-1)+fibA(n-2); // Retornar fibA(n-1)+fibA(n-2)
}
}
}
Todo parece ir bien, pero … la implementación fib1 tiene los siguientes problemas:
1. El tipo de datos int sólo permite representar números enteros hasta (es decir,
hasta 2147483647), porque utiliza sólo 32 bits para su representación. Por lo tanto, todas las
operaciones que superen este umbral arrojarían resultados erróneos, hecho que se conoce
como desbordamiento del tipo de datos. Por esta razón es que el Fibonacci de 47 da un valor
extraño (-1323752223), que no corresponde con el resultado que debería tener:
1134903170+1836311903=2971215073.
2. Se demora muchísimo para valores de cercanos a .
BigInteger es una clase de Java que nos permite operar con números de precisión arbitraria
†
.
†
La documentación de la clase BigInteger está disponible en el API de Java en el sitio
http://java.sun.com/javase/6/docs/api/
ESTRUCTURAS DE DATOS 4
Tabla 4: Algunos servicios provistos por la clase BigInteger.
Operación Descripción
BigInteger.valueOf(x) Convierte un número x de tipo long a un número de tipo BigInteger.
new BigInteger(s)
Convierte un número s almacenado como cadena de texto a un número
de tipo BigInteger.
a.add(b)
Retorna un nuevo BigInteger con el resultado de a+b (la suma), donde
a y b son dos números de tipo BigInteger.
a.subtract(b)
Retorna un nuevo BigInteger con el resultado de a-b (la resta), donde
a y b son dos números de tipo BigInteger.
Retorna un nuevo BigInteger con el resultado de a*b (la
a.multiply(b)
multiplicación), donde a y b son dos números de tipo BigInteger.
Retorna un nuevo BigInteger con el resultado de a/b (la división
a.divide(b)
entera), donde a y b son dos números de tipo BigInteger.
Retorna un nuevo BigInteger con el resultado de a%b (el residuo
a.mod(b)
entero), donde a y b son dos números de tipo BigInteger.
Retorna un nuevo BigInteger con el máximo común divisor de a y b,
a.gcd(b)
donde a y b son dos números de tipo BigInteger.
Retorna un nuevo BigInteger con el mínimo valor entre a y b, donde a
a.min(b)
y b son dos números de tipo BigInteger.
Retorna un nuevo BigInteger con el máximo valor entre a y b, donde a
a.max(b)
y b son dos números de tipo BigInteger.
Retorna verdadero si los números a y b son iguales, y retorna falso si
a.equals(b)
son distintos, donde a y b son dos números de tipo BigInteger.
Contando con BigInteger ahora sí somos capaces de hacer operaciones con números
grandes, como
761809243486409043837*2046696616531860150-4525695587262334931605*324298040889334
escribiendo un programa muy sencillo
ESTRUCTURAS DE DATOS 5
Corrigiendo el defecto de desbordamiento obtenemos una nueva versión de la
implementación de Fibonacci, que da solución a nuestro primer inconveniente.
Para saber qué tan demorada es la versión fibB codificaremos un programa capaz de
exportar una tabla csv que muestre cuántos milisegundos tarda la función fibB calculando
cada uno de los Fibonacci’s desde hasta .
ESTRUCTURAS DE DATOS 6
if (n==0) { // Si n es cero
return new BigInteger("0"); // Retornar 0
}
else if (n==1) { // Si n es uno
return new BigInteger("1"); // Retornar 1
}
else { // Si n es mayor o igual que dos
return fibB(n-1).add(fibB(n-2)); // Retornar fibB(n-1)+fibB(n-2)
}
}
}
Observe que la función que describe el consumo de tiempo de la función fibB versus es
una función exponencial. Más precisamente, se puede demostrar que la forma de esta
función es una constante multiplicada por , donde es una constante conocida en
el mundo matemático como phi o número de oro, y que es aproximadamente igual a 1.61.
¿Qué significa esa rara que se colocó? En términos informales, se dice que un
algoritmo es (lo que se lee textualmente de ) si el tiempo que se demora el
algoritmo para resolver un problema de tamaño está por debajo de un múltiplo constante
de la función . Esta notación se conoce en español como la notación de la gran , y en
inglés como Big-Oh notation.
ESTRUCTURAS DE DATOS 7
}
return a; // Retorne el valor de la variable 'a'.
}
El fragmento de código
BigInteger c=a.add(b); // La variable auxiliar c guarda el valor de a+b.
a=b; // A la variable 'a' asígnele el valor de la variable 'b'.
b=c; // A la variable 'b' asígnele el valor de la variable 'c'.
hace una suma y tres asignaciones. Por lo tanto su complejidad temporal es , porque
ejecuta un número constante de operaciones. En general, todo programa que ejecute un
número constante de operaciones tiene complejidad temporal .
La inicialización
BigInteger a=new BigInteger("0"),b=new BigInteger("1");
crea dos números y hace dos asignaciones. Entonces, también tiene complejidad .
El retorno
return a; // Retorne el valor de la variable 'a'.
simplemente entrega el valor de la variable a como resultado. Su complejidad también es
.
Y finalmente, el ciclo
for (int i=0; i<n; i++) { // Ejecutar exactamente n veces el siguiente proceso:
BigInteger c=a.add(b); // La variable auxiliar c guarda el valor de a+b.
a=b; // A la variable 'a' asígnele el valor de la variable 'b'.
b=c; // A la variable 'b' asígnele el valor de la variable 'c'.
}
hace exactamente iteraciones, donde en cada una de éstas se efectúa un número
constante de operaciones. Se concluye pues que la complejidad temporal del ciclo es ,
porque ejecuta un número de operaciones que siempre está por debajo de una constante
multiplicada por .
Formúlese la siguiente pregunta: ¿Qué es mejor: la función fibB con complejidad temporal
) donde , o la función fibC con complejidad temporal ?
Obviamente es mejor la función fibC porque su consumo de tiempo es menor, dado que la
función lineal es más pequeña que la función exponencial cuando
es grande.
ESTRUCTURAS DE DATOS 8
¿Recuerda que el método fibB se demoró calculando 18.3 minutos el Fibonacci de ?
Para que note la diferencia, ¡la versión fibC se demoró menos de un milisegundo entregando
el mismo resultado!
2. MEMOIZATION
Gráfica 10: Evidencia que muestra que el método fibB repite cálculos.
fib(4) Aquí se repitió el
cálculo de fib(2)
fib(3) fib(2)
fib(1) fib(0)
Para resolver este problema se puede aplicar una técnica conocida como Memoization, que
consiste en crear una estructura de datos adicional cuyo propósito sea guardar los resultados
de los llamados recursivos de tal forma que no se repitan cálculos innecesarios.
Con la ayuda de una tabla es posible memorizar los valores retornados por la función de
Fibonacci.
ESTRUCTURAS DE DATOS 9
}
// De lo contrario, si tabla[n]==null es porque el fibonacci de n aún no ha
// sido calculado.
else {
if (n==0) { // Si n es cero.
BigInteger res=BigInteger.valueOf(0); // Calcular el fibonacci de 0, que es 0.
tabla[n]=res; // Guardar en la tabla el fibonacci de 0, para memorizarlo.
return res; // Retornar el resultado.
}
else if (n==1) { // Si n es uno.
BigInteger res=BigInteger.valueOf(1); // Calcular el fibonacci de 1, que es 1.
tabla[n]=res; // Guardar en la tabla el fibonacci de 1, para memorizarlo.
return res; // Retornar el resultado.
}
else { // Si n es mayor o igual a dos.
BigInteger res=fibD(n-1).add(fibD(n-2)); // Calcular el fibonacci de n.
tabla[n]=res; // Guardar en la tabla el fibonacci de n, para memorizarlo.
return res; // Retornar el resultado.
}
}
}
El juego de las torres de Hanoi está conformado por tres columnas verticales y un conjunto
de discos de diámetros distintos que tienen un orificio en el centro que coincide con el
grosor de las columnas.
ESTRUCTURAS DE DATOS 10
Gráfica 12: Insumos para el juego con : cinco discos de diferente diámetro y tres columnas.
Por simplicidad, las columnas se etiquetan con las letras A, B y C, donde la columna A es la
columna inicial, la columna B es la columna intermedia, y la columna C es la columna final. Al
principio, todos los discos se encuentran apilados en la primera columna (la columna A),
ordenados por diámetro, comenzando con el de mayor diámetro y terminando con el de
menor diámetro.
El objetivo del juego consiste en trasladar todos los discos de la columna inicial (la A) hacia la
columna final (la C) mediante una serie de movimientos que deben seguir tres reglas:
Regla 1: sólo se puede mover un disco a la vez.
Regla 2: no se puede colocar un disco encima de un disco de diámetro menor.
Regla 3: no se puede trasladar un disco que tenga otros discos encima suyo.
ESTRUCTURAS DE DATOS 11
Quiero mover discos de la columna A a la columna C usando la columna B como auxiliar.
ESTRUCTURAS DE DATOS 12
Pseudocódigo de la solución:
Algoritmo solucionarHanoi(A,B,C,n)
// A es la columna inicial, B es la columna intermedia, C es la columna final
// n es el número de discos a mover
si n>0 entonces:
// Trasladar recursivamente n-1 discos de A a B usando como intermedia la C
solucionarHanoi(A,C,B,n-1)
// Trasladar un disco de A a C
trasladarDisco(A,C)
// Trasladar recursivamente n-1 discos de B a C usando como intermedia la A
solucionarHanoi(B,A,C,n-1)
fin si
Código 14: Traducción a Java del pseudocódigo, imprimiendo los movimientos a desarrollar en la consola del sistema.
public class HanoiConsola {
public static void main(String[] args) {
solucionar('A','B','C',5); // Llamado inicial con 5 discos
}
public static void solucionar(char A, char B, char C, int n) {
if (n>0) {
solucionar(A,C,B,n-1);
System.out.println("Movimiento "+A+"->"+C);
solucionar(B,A,C,n-1);
}
}
}
Sea el número exacto de movimientos que hace el algoritmo para solucionar un juego
de torres de Hanoi con discos. Por ejemplo, sería el número de movimientos para
trasladar discos, sería el número de movimientos para trasladar discos,
sería el número de movimientos para trasladar discos y sería el número de
movimientos para trasladar discos.
ESTRUCTURAS DE DATOS 13
A una ecuación como la anterior se le llama relación de recurrencia. ¿Crecerá más que la
función ? ¿Crecerá menos que la función ?. Para poder comparar
cómodamente contra otras funciones, es necesario encontrar una fórmula cerrada que
la describa, es decir, una fórmula que no tenga llamados recursivos. Para tal efecto,
seguiremos una receta útil que consta de dos pasos:
Paso 1: suponga que es grande y aplique la función varias veces hasta que encuentre
un patrón.
porque
porque
distribuyendo y simplificando
porque
distribuyendo y simplificando
porque
distribuyendo y simplificando
porque
distribuyendo y simplificando
...
generalizando la fórmula donde es
el número de paso
Hemos demostrado que el número exacto de movimientos que realiza el algoritmo para
solucionar un juego de torres de Hanoi con discos es exactamente . Por tanto, para
discos, el programa debe hacer movimientos ( jugadas). Se concluye pues que la
complejidad temporal del algoritmo es .
El proyecto en Eclipse Hanoi.zip provee una animación gráfica del algoritmo que soluciona el
juego de Hanoi.
ESTRUCTURAS DE DATOS 14
3.2. BÚSQUEDA BINARIA (BINARY SEARCH)
Recurso como proyecto en Eclipse: BusquedaBinaria.zip.
El proceso de búsqueda tradicional, llamado Búsqueda Lineal, consiste en pasar posición por
posición de un arreglo para buscar el valor requerido
El método
public static int busquedaLineal(long[] pArreglo, int pInf, int pSup, long pValor)
entrega como respuesta la posición donde se encuentra el valor pValor dentro del arreglo
pArreglo, considerando posiciones del arreglo desde pInf (inclusive) hasta pSup (inclusive).
En caso de que el valor aparezca varias veces en el arreglo, se entrega la menor posición
donde se encuentre; y en caso de que el valor no aparezca, se retorna -1. La complejidad del
algoritmo es , donde es la cantidad de elementos en la porción de arreglo a
inspeccionar, porque en el peor de los casos se deberán procesar todas las posiciones del
arreglo en cuestión.
¿Se podría implementar un algoritmo más eficiente si sabemos que el arreglo está ordenado?
Imagine un diccionario de trescientas páginas que tiene definiciones de diez mil palabras del
idioma español. Si el diccionario estuviera desordenado, no nos queda otro camino que
leerlo todo para determinar si una palabra está o no está. Pero como todos sabemos que los
términos del diccionario están ordenados alfabéticamente, podemos buscar una palabra más
rápidamente mediante una destreza que aprendimos desde la infancia: buscar en un
diccionario o en un directorio telefónico. Suponga que estamos buscando la palabra Faro y
que abrimos el diccionario justo en la mitad, encontrando la palabra Jarro. Sólo con esto
sabemos que Faro está en la primera mitad del diccionario y no en la segunda, porque Faro
está antes que Jarro. Luego, abrimos el diccionario en medio de la primera mitad y
encontramos la palabra Doncella. Sabemos entonces que Faro está después de Doncella y
antes que Jarro, lo que nos deja con un cuarto del total del diccionario. Siguiendo este
proceso de manera sucesiva llegamos a la página donde debe estar Faro, y dentro de la
página hacemos lo mismo para encontrar el lugar preciso donde aparece el término. El
proceso llevado a cabo se conoce en computación como Búsqueda Binaria, y como pudo
notarlo, es mucho más eficiente que la Búsqueda Lineal, ¿pero qué tanto?
ESTRUCTURAS DE DATOS 15
Gráfica 16: Diagrama de flujo para el algoritmo de Búsqueda Binaria.
ESTRUCTURAS DE DATOS 16
// Buscar el valor en la mitad de la izquierda:
return busquedaBinaria(pArreglo,pInf,mitad-1,pValor);
}
// Si el valor buscado es mayor que lo que está en la mitad:
else {
// Buscar el valor en la mitad de la derecha:
return busquedaBinaria(pArreglo,mitad+1,pSup,pValor);
}
}
}
Paso 1: suponga que es grande y aplique la función varias veces hasta que encuentre
un patrón.
porque
porque
simplificando
porque
simplificando
porque
simplificando
porque
simplificando
...
generalizando la fórmula donde
es el número de paso
ESTRUCTURAS DE DATOS 17
Entonces, la complejidad temporal del algoritmo de Búsqueda Binaria es , lo que
demuestra que es más eficiente que el algoritmo de Búsqueda Lineal, cuya complejidad
temporal es .
Para ordenar arreglos se cuenta con un proceso muy eficiente llamado algoritmo de
Ordenamiento por Mezcla (Merge Sort en inglés).
ESTRUCTURAS DE DATOS 18
Paso 1: suponga que es grande y aplique la función varias veces hasta que encuentre
un patrón.
porque
porque
distribuyendo y simplificando
porque
distribuyendo y simplificando
porque
distribuyendo y simplificando
porque
distribuyendo y simplificando
...
generalizando la fórmula donde
es el número de paso
4. BACKTRACKING
ESTRUCTURAS DE DATOS 19
construir todas las posibilidades que resultan de una combinación parcialmente construida si
se sabe que generará posibilidades que violan las restricciones del problema.
EN RESUMEN
Una función es recursiva si está definida en términos de sí misma.
La definición de una función recursiva debe estar formada por:
Casos base: son casos triviales en los que la definición de la función no depende de la función
misma.
Casos recursivos: son casos complejos en los que la definición de la función referencia a la función
misma.
Una función es cerrada si no está definida en términos de sí misma.
BigInteger es una clase de Java que nos permite operar con números de precisión arbitraria.
La complejidad temporal es una medida de qué tanto tiempo consume un algoritmo en su ejecución.
La complejidad temporal sirve para medir la eficiencia de un programa.
Todo programa que ejecute un número constante de operaciones tiene complejidad temporal .
Todo programa que ejecute un número de operaciones que siempre esté por debajo de una
constante multiplicada por , tiene complejidad temporal .
La técnica Memoization consiste en crear una estructura de datos adicional cuyo propósito sea
guardar los resultados de los llamados recursivos de tal forma que no se repitan cálculos innecesarios.
Dividir y Conquistar, también conocida como Dividir y Vencer, es una técnica que consiste en dividir
un problema en subproblemas similares más pequeños, solucionar tales subproblemas y unir estas
soluciones para resolver el problema original.
El algoritmo de Búsqueda Binaria es un proceso eficiente para buscar valores en arreglos ordenados.
El Backtracking es una técnica de búsqueda por fuerza bruta que consiste en iterar sobre todas las
posibilidades hasta que se encuentre una solución adecuada al problema, descartando en masa
conjuntos de posibilidades sin haberlas construido explícitamente, que se sabe que no van a llegar a
la solución.
ESTRUCTURAS DE DATOS 20
ESTRUCTURAS DE DATOS
UNIDAD UNO - SEMANA DOS
ANÁLISIS DE ALGORITMOS *
TABLA DE CONTENIDO
1. MOTIVACIÓN 2
2. COMPLEJIDAD TEMPORAL Y COMPLEJIDAD ESPACIAL 2
3. NOTACIÓN O 3
4. ANÁLISIS DE COMPLEJIDAD DE PROGRAMAS ITERATIVOS 3
5. ANÁLISIS DE COMPLEJIDAD DE PROGRAMAS RECURSIVOS 5
6. TIPOS DE ANÁLISIS DE COMPLEJIDAD 6
EN RESUMEN 6
PARA TENER EN CUENTA 6
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. MOTIVACIÓN
Hay dos mecanismos para medir complejidad: el teórico y el práctico. El mecanismo práctico
es completamente inviable, puesto que hay computadores más rápidos que otros y el tiempo
de ejecución depende de factores como el sistema operativo de la máquina, el lenguaje de
programación, el compilador utilizado, los procesos que se estén corriendo en el computador
en el momento de la prueba (como juegos y antivirus), etc.
ESTRUCTURAS DE DATOS 2
3. Encuentre una función que relacione el tamaño del problema con el número de
operaciones básicas ejecutadas, analizando el peor escenario posible. Esta función refleja la
complejidad temporal, porque entre más operaciones se ejecuten, más tiempo se demoraría
el algoritmo en ejecutar.
3. NOTACIÓN O
ESTRUCTURAS DE DATOS 3
instrucción m;
halle la complejidad de cada instrucción por separado y quédese con la más grande de éstas.
Para calcular la complejidad temporal de un ciclo while o for en Java multiplique el número
de iteraciones que hace el ciclo por la complejidad del cuerpo del ciclo.
Tabla 1: Ejemplos que buscan enseñar a calcular complejidades temporales de forma intuitiva.
Programa Complejidad temporal
double x=7,y=x+3,z=0;
if (x>=6) {
z=x+y;
} porque hace un número constante de operaciones.
else {
z=x-y;
}
int f01(int n) {
int r=0,i=0; porque el ciclo realiza iteraciones y la complejidad
while (i<n) {
r+=i; del cuerpo
i++; r+=i;
} i++;
return r; es .
}
int f02(int n) {
porque el ciclo realiza iteraciones y la complejidad
int r=0; del cuerpo
for (int i=0; i<n; i++) { r+=i;
r+=i; i++;
} es .
return r;
Nótese que este for es traducción del while del ejemplo
}
anterior.
int f03(int n, int m) {
int r=0;
for (int i=0; i<n; i++) {
for (int j=0; j<m; j++) {
porque los dos ciclos están anidados, el primer
r+=i*j;
} ciclo hace iteraciones, y el segundo hace iteraciones.
}
return r;
}
ESTRUCTURAS DE DATOS 4
int f04(int n) { porque los dos ciclos están anidados, el primer ciclo
int r=0;
for (int i=0; i<n; i++) {
hace iteraciones, y el segundo nunca hace más de
for (int j=i; j<n; j++) { iteraciones. Observe que el número de iteraciones del
r+=i*j; segundo ciclo depende del valor de i porque j varía desde
} i hasta n-1. ¡No se complique!: como estamos analizando
}
return r; el peor caso, nos contentamos con decir que el segundo
} ciclo nunca hace más de iteraciones, lo que es cierto.
int f05(int n) {
int r=0;
for (int i=0; i<n; i++) {
r+=i; porque un ciclo está después del otro, el primer ciclo
} hace iteraciones, y el segundo hace también
for (int j=0; j<n; j++) { iteraciones. Además sabemos que la función es
r+=j;
} porque es un múltiplo constante de .
return r;
}
int f06(int n, int m) {
int r=0;
for (int i=0; i<n; i++) {
r+=i*i+3;
} porque un ciclo está después del otro, el primer
for (int j=0; j<m; j++) { ciclo hace iteraciones, y el segundo hace iteraciones.
r+=j*5-2;
}
return r;
}
int f07(int n) {
int r=0;
if (n>=15) {
for (int i=1; i<n; i++) {
for (int j=i; j<n; j++) {
r+=i-j;
}
}
} . ¿Por qué? Piense en el peor caso.
else {
for (int i=1; i<n; i++) {
r+=i;
}
}
return r;
}
ESTRUCTURAS DE DATOS 5
6. TIPOS DE ANÁLISIS DE COMPLEJIDAD
EN RESUMEN
Dado cierto recurso, la complejidad de un algoritmo es la cantidad de recurso que el programa
demanda para su ejecución. La demanda de procesador se mide con la complejidad temporal,
mientras que la demanda de memoria RAM se mide con la complejidad espacial.
Calcular la complejidad de nuestros programas nos sirve para:
Medir los recursos que gasta un algoritmo.
Estimar la demanda de recursos que necesita un programa.
Comparar programas para saber cuál es más eficiente en tiempo o en espacio utilizado.
La complejidad temporal mide el tiempo gastado y la complejidad espacial mide el espacio requerido.
Para medir complejidades se utiliza la notación , que se centra en un análisis en el peor caso.
Todo programa que ejecute un número de operaciones que siempre esté por debajo de una
constante multiplicada por (para valores grandes de ), tiene complejidad temporal .
ESTRUCTURAS DE DATOS 6
GUÍA DE COMPETENCIAS Y ACTIVIDADES
SEMANA 3
TEMA(S): NÚCLEO TEMÁTICO: ESTRUCTURAS DE DATOS LINEALES
1. Teoría e implementación de estructuras de datos lineales
1.1. Listas.
1.1.1. Vectores.
2. Estructuras lineales en la librería estándar de Java.
2.1. Listas: Estudio de la interfaz List<E> y de las clases Vector<E>, ArrayList<E> y
LinkedList<E>.
1 [ POLITÉCNICO GRANCOLOMBIANO]
4- Abstraer información y comportamiento de objetos del mundo real, utilizando
herramientas formales, para la construcción de modelos que permitan el diseño de
soluciones.
5- Plantear, diseñar e implementar Sistemas de Tecnología, Información y
Telecomunicaciones capaces de resolver una problemática en un contexto dado, con
restricciones identificadas y recursos definidos.
6- Identificar cuales variables o parámetros son los más relevantes en la dinámica del
sistema, y así mismo descartar aspectos irrelevantes, o de poca incidencia con el fin de
llegar a modelos matemáticos que permitan soluciones analíticas.
Lectura 3-2 - Tres Leer y comprender los conceptos expuestos en las lecturas.
Las listas en el
lenguaje Java.
Lectura 3-3 - Tres Leer y comprender los conceptos expuestos en las lecturas.
Implementaciones de
listas (parte 1).
[ ESTRUCTURA DE DATOS ] 2
Video diapositivas 1 - Leer y comprender los conceptos expuestos en los videos diapositivas.
Operaciones básicas Tres
sobre listas.
Ejercicios propuestos. Tres Desarrollar de forma individual algunos de los ejercicios propuestos para la
semana.
Recursos adicionales. Tres Revisar cuidadosamente el material en Java organizado como proyectos en
Eclipse, con el fin de afianzar la práctica.
SEMANA 4
TEMA(S): NÚCLEO TEMÁTICO: ESTRUCTURAS DE DATOS LINEALES
1. Teoría e implementación de estructuras de datos lineales
1.1. Listas.
1.1.1. Vectores.
2. Estructuras lineales en la librería estándar de Java.
2.1. Listas: Estudio de la interfaz List<E> y de las clases Vector<E>, ArrayList<E> y
LinkedList<E>.
3 [ POLITÉCNICO GRANCOLOMBIANO]
1.5. Colas de prioridad.
2. Estructuras lineales en la librería estándar de Java.
2.2. Pilas: Estudio de la clase Stack<E>.
2.3. Colas: Estudio de la interfaz Queue<E> y de la clase LinkedList<E>.
2.4. Colas de prioridad: Estudio de la clase PriorityQueue<E>.
[ ESTRUCTURA DE DATOS ] 4
NÚCLEO TEMÁTICO: ESTRUCTURAS DE DATOS LINEALES
1. Teoría e implementación de estructuras de datos lineales
1.1. Listas.
1.1.2. Listas doblemente encadenadas en anillo con encabezado.
1.1.3. Listas doblemente encadenadas.
1.1.4. Listas sencillamente encadenadas.
1.2. Listas ordenadas.
1.3. Pilas.
1.4. Colas.
1.5. Colas de prioridad.
2. Estructuras lineales en la librería estándar de Java.
2.2. Pilas: Estudio de la clase Stack<E>.
2.3. Colas: Estudio de la interfaz Queue<E> y de la clase LinkedList<E>.
2.4. Colas de prioridad: Estudio de la clase PriorityQueue<E>.
Lectura 4-2 - Cuatro Leer y comprender los conceptos expuestos en las lecturas.
Iteradores sobre listas.
Lectura 4-3 - Cuatro Leer y comprender los conceptos expuestos en las lecturas.
Otras estructuras de
datos lineales.
Video diapositivas 1 - Cuatro Leer y comprender los conceptos expuestos en los videos diapositivas.
Notación infija.pptx.
Video diapositivas 2 - Cuatro Leer y comprender los conceptos expuestos en los videos diapositivas.
Notación posfija.pptx.
Ejercicios propuestos. Cuatro Desarrollar de forma individual algunos de los ejercicios propuestos para la
semana.
Recursos adicionales. Cuatro Revisar cuidadosamente el material en Java organizado como proyectos en
Eclipse, con el fin de afianzar la práctica.
5 [ POLITÉCNICO GRANCOLOMBIANO]
ESTRUCTURAS DE DATOS
UNIDAD DOS - SEMANA TRES
LAS LISTAS COMO ESTRUCTURAS DE DATOS *
TABLA DE CONTENIDO
1. DEFINICIÓN 2
2. APLICABILIDAD 2
3. CONCEPTOS BÁSICOS 2
3.1. POSICIÓN 2
3.2. TAMAÑO 3
3.3. ORDINALES 3
3.4. SIGUIENTE 3
3.5. ANTERIOR 3
3.6. RECORRIDOS 4
3.7. IGUALDAD 4
3.8. CONTENENCIA 4
3.9. SUBLISTAS 4
3.10. EJEMPLOS 4
4. OPERACIONES BÁSICAS 6
EN RESUMEN 7
PARA TENER EN CUENTA 8
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. DEFINICIÓN
Una lista es una secuencia finita de elementos del mismo tipo. Por medio de la notación
Es importante tener en cuenta que en toda lista importa el orden en el que se encuentran
sus elementos.
2. APLICABILIDAD
Las listas tienen gran aplicabilidad como estructuras de datos porque nos permiten
almacenar elementos en secuencia, un elemento seguido de otro. En el mundo cotidiano
tenemos listas por doquier, por ejemplo:
La lista de alumnos registrados en una asignatura.
La lista de asistencia a clase.
La lista de personas que asisten a un evento.
La lista de actividades a realizar en el día.
La lista que contiene los días de la semana:
3. CONCEPTOS BÁSICOS
3.1. POSICIÓN
Todos los elementos de una lista se pueden acceder por posición. En la lista
ESTRUCTURAS DE DATOS 2
3.2. TAMAÑO
El tamaño o longitud de la lista se define como el número de elementos que almacena. Así
pues, la lista tendría elementos, y la lista vacía tendría elementos.
3.3. ORDINALES
Nótese en todo caso, que el primer elemento de la lista se encuentra en la posición cero y
que el último elemento se encuentra en la posición correspondiente al tamaño de la lista
menos uno.
3.4. SIGUIENTE
Cada elemento de la lista excepto el último tiene un siguiente elemento. Por ejemplo, el
siguiente del primero es el segundo, el siguiente del segundo es el tercero, etc. En general, el
siguiente elemento del que está en la posición es el que está en la posición .
3.5. ANTERIOR
Cada elemento de la lista excepto el primero tiene un anterior elemento. Por ejemplo, el
anterior del segundo es el primero, el anterior del tercero es el segundo, etc. En general, el
anterior elemento del que está en la posición es el que está en la posición .
ESTRUCTURAS DE DATOS 3
3.6. RECORRIDOS
Un recorrido de una lista es una forma de visitar todos los elementos de la lista. Dos posibles
recorridos de una lista son:
Recorrido al derecho: visita los elementos de la lista en el orden que aparecen, comenzando
por el primer elemento y terminando en el último.
Recorrido al revés: visita los elementos de la lista en el orden contrario al que aparecen,
comenzando por el último elemento y terminando en el primero.
3.7. IGUALDAD
Dos listas son iguales si y sólo si tienen el mismo tamaño y almacenan los mismos elementos
en las mismas posiciones.
3.8. CONTENENCIA
Una lista está contenida dentro de otra si y sólo si todo elemento de la primera está presente
dentro de la segunda, así sea en distinto orden.
3.9. SUBLISTAS
Una lista es sublista de otra si y sólo si todos los elementos de la primera aparecen en el
mismo orden a partir de alguna posición de la segunda.
3.10. EJEMPLOS
Tabla 1: Ejemplos sobre los conceptos básicos.
Concepto Lista Ejemplo
El elemento de la posición de la lista está indefinido,
porque esta posición se encuentra por fuera de la lista.
El elemento de la posición de la lista es .
El elemento de la posición de la lista es .
Posición
El elemento de la posición de la lista está indefinido,
porque esta posición se encuentra por fuera de la lista.
El elemento de la posición de la lista está indefinido,
porque esta posición se encuentra por fuera de la lista.
El elemento de la posición de la lista es .
Posición
El elemento de la posición de la lista es .
ESTRUCTURAS DE DATOS 4
El elemento de la posición de la lista es .
El elemento de la posición de la lista es .
El elemento de la posición de la lista es .
El elemento de la posición de la lista es .
El elemento de la posición de la lista está indefinido,
porque esta posición se encuentra por fuera de la lista.
Tamaño El tamaño de la lista es porque la lista está vacía.
Tamaño El tamaño de la lista es .
Tamaño El tamaño de la lista es .
Tamaño El tamaño de la lista es .
El primer elemento de la lista es el (es también el
penúltimo).
Ordinales
El segundo elemento de la lista es el (es también el
último).
El primer elemento de la lista es el .
El segundo elemento de la lista es el .
El tercer elemento de la lista es el .
El cuarto elemento de la lista es el (es también el
Ordinales antepenúltimo).
El quinto elemento de la lista es el (es también el
penúltimo).
El sexto elemento de la lista es el (es también el
último).
El siguiente del elemento es el elemento .
Siguiente El siguiente del elemento es el elemento .
El elemento no tiene siguiente porque es el último.
El siguiente del elemento no está definido porque el
Siguiente
valor aparece dos veces dentro de la lista.
El elemento no tiene anterior porque es el primero.
Anterior El anterior del elemento es el elemento .
El anterior del elemento es el elemento .
El anterior del elemento no está definido porque el
Anterior
valor aparece dos veces dentro de la lista.
Al derecho: visita los elementos en el orden .
Recorridos
Al revés: visita los elementos en el orden .
Al derecho: visita los elementos en el orden
.
Recorridos
Al revés: visita los elementos en el orden
.
ESTRUCTURAS DE DATOS 5
NO NO NO
NO NO NO
NO SI NO
NO SI SI
NO NO NO
NO SI SI
NO SI SI
NO SI SI
NO NO NO
SI SI SI
NO SI NO
4. OPERACIONES BÁSICAS
ESTRUCTURAS DE DATOS 6
Inserción del elemento en la posición .
Inserción
(inserción al principio)
Inserción Inserción del elemento en la posición .
EN RESUMEN
Una lista es una secuencia finita de elementos del mismo tipo.
La lista vacía no tiene ningún elemento.
Todos los elementos de una lista se pueden acceder por posición.
El tamaño o longitud de la lista se define como el número de elementos que almacena.
El primer elemento de la lista es el que se encuentra en la posición , el segundo elemento es el
que se encuentra en la posición , el tercer elemento es el que se encuentra en la posición , etc.
El último elemento de la lista es el que se encuentra en la posición , el penúltimo elemento es
el que se encuentra en la posición , el antepenúltimo elemento es el que se encuentra en la
posición , etc.
Un recorrido de una lista es una forma de visitar todos los elementos de la lista.
Dos listas son iguales si y sólo si tienen el mismo tamaño y almacenan los mismos elementos en las
mismas posiciones.
ESTRUCTURAS DE DATOS 7
Una lista está contenida dentro de otra si y sólo si todo elemento de la primera está presente
dentro de la segunda, así sea en distinto orden.
Una lista es sublista de otra si y sólo si todos los elementos de la primera aparecen en el mismo
orden a partir de alguna posición de la segunda.
Para administrar el contenido de una lista existen cuatro operaciones básicas: consulta,
modificación, inserción y eliminación.
ESTRUCTURAS DE DATOS 8
ESTRUCTURAS DE DATOS
UNIDAD DOS - SEMANA TRES
LAS LISTAS EN EL LENGUAJE JAVA *
TABLA DE CONTENIDO
1. LA INTERFAZ LIST<E> 2
2. IMPLEMENTACIONES SUMINISTRADAS POR JAVA PARA LA INTERFAZ LIST<E> 2
3. SERVICIOS PROVISTOS POR LA INTERFAZ LIST<E> 4
EN RESUMEN 6
PARA TENER EN CUENTA 6
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. LA INTERFAZ LIST<E>
El tipo E es genérico: puede ser cualquier clase en Java. Por ejemplo, List<Integer>
representa una lista de números enteros, List<Double> representa una lista de números
flotantes, List<String> representa una lista de cadenas de texto, List<Persona> representa
una lista de personas, List<Circulo> representa una lista de círculos, etc.
†
La interfaz List<E> se encuentra ubicada en el paquete java.util, y su documentación en inglés está
disponible en el API de Java en la página http://java.sun.com/javase/6/docs/api/java/util/List.html.
ESTRUCTURAS DE DATOS 2
Gráfica 2: Representación de la lista con la clase ArrayList<E> de Java.
0 1 2 n-1 n t-1
Arreglo ... ... Tamaño
Anillo
...
...
La única diferencia entre ArrayList<E> y Vector<E> es que los métodos de la clase Vector<E>
están sincronizados, mientras que los de la clase ArrayList<E> no. Para describir qué significa
esto hay que hablar sobre Hilos de ejecución (mejor conocidos en inglés como Threads).
Resulta que, en lenguajes como Java y C++, un programa puede estar formado por varios
Threads, donde todos ejecutan en paralelo varias tareas a la vez. Imagínese dos Threads
accediendo sobre el mismo objeto: una cuenta bancaria que tiene tres mil euros. Suponga
que el primer Thread comienza a extraer de la cuenta bancaria dos mil euros y que justo en
el mismo momento, el segundo Thread intenta extraer también dos mil euros de la misma
cuenta. Bajo este escenario puede suceder que ambas extracciones de dinero sean exitosas,
lo que permitiría sacar cuatro mil euros sobre una cuenta bancaria que sólo tenía tres mil. La
sincronización soluciona este problema: evita que dos Threads ejecuten en el mismo instante
de tiempo dos operaciones sobre el mismo objeto, obligándolos a que hagan cola uno detrás
del otro para que tengan permiso de acceder al objeto sin estorbarse entre sí.
ESTRUCTURAS DE DATOS 3
Gráfica 5: Peligros de no sincronizar los métodos de una clase.
Inicio Thread 1 Saldo inicial de la cuenta bancaria: 3000 euros
Monto del retiro: 2000 euros
Inicio Thread 2 Consultar saldo: 3000 euros
¿El saldo es mayor o igual que Monto del retiro: 2000 euros
el monto del retiro?: SI Consultar saldo: 3000 euros
Entregar dinero al cliente … ¿El saldo es mayor o igual que
THREAD #1 Restarle al saldo el monto el monto del retiro?: SI
del retiro: 3000€-2000€=1000€ Entregar dinero al cliente …
THREAD #2
Fin de la transacción Restarle al saldo el monto
del retiro: 3000€-2000€=1000€
Fin de la transacción
Monto total retirado: 4000 euros
E get(int index)
Retorna el elemento de la posición index de la
lista.
Operaciones de modificación sobre la lista
Modifica el elemento de la posición index de la
lista por el valor element.
E set(int index, E element) Retorna como resultado el valor que se
encontraba en la posición index antes de
realizar la modificación.
Operaciones de búsqueda sobre la lista
boolean contains(Object obj)
Retorna true si el objeto obj está dentro de la
lista; retorna false si no.
Retorna true si todos los objetos de la
boolean containsAll(Collection<?> coll) colección coll están dentro de la lista; retorna
false si no.
Retorna el índice de la primera aparición del
int indexOf(Object obj) objeto obj dentro de la lista. En caso de que el
objeto obj no esté en la lista, retorna -1.
Retorna el índice de la última aparición del
int lastIndexOf(Object obj) objeto obj dentro de la lista. En caso de que el
objeto obj no esté en la lista, retorna -1.
Operaciones de inserción sobre la lista
boolean add(E element)
Inserta el elemento element al final de la lista.
Retorna true en todo caso.
ESTRUCTURAS DE DATOS 4
void add(int index, E element)
Inserta el elemento element en la posición
index de la lista.
Inserta todos los elementos de la colección
coll al final de la lista.
boolean addAll(Collection<? extends E> coll)
Retorna true si la lista fue modificada por la
operación; retorna false si no.
Inserta todos los elementos de la colección
boolean addAll(int index, coll a partir de la posición index de la lista.
Collection<? extends E> coll) Retorna true si la lista fue modificada por la
operación; retorna false si no.
Operaciones de eliminación sobre la lista
void clear() Elimina todos los elementos de la lista.
Elimina el elemento de la posición index de la
E remove(int index)
lista.
Retorna el valor que precisamente fue
eliminado.
Elimina la primera aparición del objeto obj
dentro de la lista. En caso de que el objeto obj
no aparezca, no se hace nada. En caso de que el
boolean remove(Object obj) objeto obj aparezca más de una vez, sólo se
elimina su primera aparición.
Retorna true si la lista fue modificada por la
operación; retorna false si no.
Elimina de la lista todos los elementos que
boolean removeAll(Collection<?> coll)
aparezcan dentro de la colección coll.
Retorna true si la lista fue modificada por la
operación; retorna false si no.
Retiene en la lista sólo los elementos que
aparezcan dentro de la colección coll. En otras
boolean retainAll(Collection<?> coll)
palabras, elimina de la lista todos los elementos
que no aparezcan dentro de la colección coll.
Retorna true si la lista fue modificada por la
operación; retorna false si no.
Métodos que entregan iteradores sobre la lista
Iterator<E> iterator()
Retorna un iterador capaz de visitar todos los
elementos de la lista.
ListIterator<E> listIterator()
Retorna un iterador capaz de visitar todos los
elementos de la lista.
Retorna un iterador capaz de visitar los
ListIterator<E> listIterator(int index) elementos de la lista, iniciando en la posición
index.
Métodos que entregan un arreglo con el contenido de la lista
Object[] toArray()
Retorna un arreglo de objetos contiendo todos
los elementos de la lista.
<T> T[] toArray(T[] array) Retorna un arreglo de objetos contiendo todos
ESTRUCTURAS DE DATOS 5
los elementos de la lista. Si el tamaño del
arreglo array es mayor o igual que el tamaño
de la lista, se usa array para guardar los
elementos de la lista.
Métodos de comparación
boolean equals(Object obj)
Retorna true si obj es una lista igual a esta
lista; retorna false si no.
Métodos de hashing
int hashCode()
Dejaremos la descripción de este método hasta
cuando estemos estudiando Tablas de Hashing.
Métodos que entregan vistas de la lista
Retorna la sublista de esta lista que va desde la
posición fromIndex hasta la posición toIndex-
1. La sublista retornada es una vista de la lista
List<E> subList(int fromIndex, int toIndex)
original, es decir, cualquier modificación sobre
la sublista retornada también tiene efecto
sobre la lista original.
EN RESUMEN
Una interfaz es un conjunto de métodos definidos sin implementación.
List<E> es una interfaz de Java que representa una lista de elementos de tipo E.
ArrayList<E> implementa listas a través de vectores (arreglos dinámicos de tamaño variable).
Vector<E> también implementa listas a través de vectores (arreglos dinámicos de tamaño variable).
LinkedList<E> implementa listas a través de nodos doblemente encadenados en anillo con
encabezado.
La interfaz List<E> define 25 métodos de propósito general para manipular listas.
ESTRUCTURAS DE DATOS 6
ESTRUCTURAS DE DATOS
UNIDAD DOS - SEMANA TRES
IMPLEMENTACIONES DE LISTAS (PARTE 1) *
TABLA DE CONTENIDO
1. INTRODUCCIÓN 2
2. DECLARACIÓN DE UNA INTERFAZ PARA LA REPRESENTACIÓN DE LISTAS 2
3. APUNTES SOBRE LA ADMINISTRACIÓN DE LA MEMORIA PRINCIPAL EN JAVA 3
4. IMPLEMENTACIÓN CON VECTORES 4
4.1. CONSTRUCTOR 5
4.2. DESTRUCTOR 6
4.3. OPERACIÓN DE CONSULTA 6
4.4. OPERACIÓN DE MODIFICACIÓN 7
4.5. OPERACIÓN DE INSERCIÓN 7
4.1. OPERACIÓN DE ELIMINACIÓN 10
EN RESUMEN 11
PARA TENER EN CUENTA 12
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. INTRODUCCIÓN
Cada vez que necesitemos una lista deberemos decidir si escoger la implementación con
vectores (ArrayList<E>) o la implementación con nodos encadenados (LinkedList<E>),
dependiendo de cuál nos dé la mejor eficiencia en el problema que estemos resolviendo.
En esta lectura codificaremos una interfaz para representar listas y cuatro implementaciones
de tal interfaz (las siglas VED significan Versión especialmente diseñada para el módulo de
Estructuras de Datos):
Tabla 1: Interfaces y clases que trataremos en el estudio de las listas como estructuras de datos.
Tipo Nombre Descripción
Interfaz para representar una lista de elementos de tipo E. Es una
Interfaz VEDList<E>
copia exacta de la interfaz List<E> de Java.
Clase que implementa listas con vectores (arreglos dinámicos de
Clase VEDArrayList<E> tamaño variable). Pretende enseñar cómo están construidas las
clases ArrayList<E> y Vector<E> de Java.
Clase que implementa listas con nodos doblemente encadenados
Clase VEDLinkedList<E> en anillo con encabezado. Pretende enseñar cómo está construida
la clase LinkedList<E> de Java.
Clase que implementa listas con nodos doblemente encadenados.
Clase VEDLinkedListD<E> Esta implementación no es provista por Java y no se distribuye su
código fuente en el recurso ImplementacionesListas.zip.
Clase que implementa listas con nodos sencillamente
encadenados. Esta implementación no es provista por Java y no se
Clase VEDLinkedListS<E>
distribuye su código fuente en el recurso
ImplementacionesListas.zip.
ESTRUCTURAS DE DATOS 2
VEDLinkedListS<E> no se incluyen puesto que son objeto de estudio de los ejercicios
prácticos propuestos para la unidad.
Las clases están documentadas siguiendo Javadoc, que es un estándar de Sun Microsystems
para documentar código en Java. Puede encontrar más información sobre Javadoc en los
siguientes enlaces:
1. How to Write Doc Comments for the Javadoc Tool:
http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
2. javadoc - The Java API Documentation Generator:
http://java.sun.com/javase/6/docs/technotes/tools/windows/javadoc.html
En Java toda variable de tipo básico (byte, short, int, long, float, double, char, boolean) se
administra por valor, es decir, se trata como un registro que almacena un valor.
Por otro lado, toda variable cuyo tipo sea una clase (String, Persona, ArrayList, etc.) se
administra por referencia, es decir, se trata como un apuntador que referencia a un objeto,
almacenando la dirección en memoria donde éste se encuentra.
Gráfica 3: Las variables cuyo tipo sea una clase se administran por referencia.
MEMORIA RAM
La constante null, que representaremos con el símbolo , actúa en Java como un apuntador
a la nada. Asignándole a un apuntador la constante null, se puede dejar de referenciar el
objeto apuntado.
ESTRUCTURAS DE DATOS 3
Gráfica 4: La constante null como apuntador a la nada.
Basura que está MEMORIA RAM
ocupando memoria
CÓDIGO FUENTE centroX
a=null; a centroY
radio
En Java existe un proceso muy importante que se llama el Recolector de Basura (en inglés:
Garbage Collector), cuyo propósito consiste en liberar la memoria ocupada por aquellos
objetos que se han dejado de referenciar por nuestro programa. El Recolector de Basura se
ejecuta cada vez que el uso de memoria sobrepase cierto umbral de porcentaje, o cada vez
que la implementación de la máquina virtual lo decida. Por esta razón no nos debemos
preocupar en Java por liberar explícitamente la memoria reservada: apenas dejemos de
referenciar uno o más objetos éstos se convierten en basura, cuya memoria será liberada por
el Recolector de Basura cuando se ejecute la próxima vez.
Formalmente hablando, en Java no existen los arreglos de tamaño variable, puesto que todos
los arreglos en Java tienen una longitud fija después de haber sido creados. ¿Y entonces qué
hacemos? La respuesta es sencilla: cada vez que necesitemos cambiar el tamaño del arreglo
creamos uno nuevo del tamaño deseado, pasamos el contenido del arreglo viejo al arreglo
nuevo, desechamos el arreglo viejo y nos quedamos con el arreglo nuevo. Este truco nos
permite simular en Java arreglos dinámicos de tamaño variable.
tamanho: es una variable de tipo int que almacena el tamaño de la lista, y que abreviaremos con la
letra . Recuerde que el tamaño de una lista es el número de elementos que almacena.
arreglo: es un arreglo de objetos que almacena todos los elementos de la lista, en el orden
que aparecen. Al tamaño del arreglo se le llama capacidad y lo abreviaremos con la letra .
ESTRUCTURAS DE DATOS 4
Siempre debe cumplirse que (que la capacidad del arreglo sea mayor o igual que el
tamaño de la lista), porque de lo contrario, los elementos de la lista no cabrían en el arreglo.
Todas las posiciones del arreglo desde hasta son casillas ocupadas por los elementos
de la lista, y todas las posiciones del arreglo desde hasta son casillas de reserva para
permitir de forma eficiente la inserción de elementos futuros.
// *************************
// * Atributos de la clase *
// *************************
/**
* Arreglo que contiene los elementos de la lista (inicia con capacidad 1).
*/
private Object[] arreglo=new Object[1];
/**
* Tamaño de la lista (inicia en 0).
*/
private int tamanho=0;
// ***********************
// * Métodos de la clase *
// ***********************
// ...
4.1. CONSTRUCTOR
El método constructor de la clase, que se responsabiliza de crear una nueva lista vacía, no
debe realizar ninguna operación adicional porque con la inicialización de los atributos ya se
está representando una lista vacía: el atributo arreglo se inicializó con un arreglo de
capacidad , y el atributo tamanho se inicializó en . No importa la capacidad inicial del
arreglo porque a medida que se vayan realizando inserciones sobre la lista, se va ajustando
su capacidad.
ESTRUCTURAS DE DATOS 5
4.2. DESTRUCTOR
Para eliminar todos los elementos de la lista existe el método clear, cuya implementación
debe asignarle a todas las casillas ocupadas del arreglo el valor null y debe ponerle el valor
al atributo tamanho. De esta manera, se dejan de referenciar los objetos de la lista,
marcándolos como basura, y dejándole el trabajo de la liberación de memoria al Recolector
de Basura.
Para consultar el elemento que se encuentra en cierta posición de la lista, basta acceder al
arreglo en la posición dada.
Código 11: Método para consultar el valor del elemento que está en determinada posición de la lista.
public E get(int index) {
// Obtener el elemento que está en la posición index del arreglo:
Object elemento=arreglo[index];
// Retornar el elemento obtenido, convertido al tipo E por medio de un cast:
return (E)elemento;
}
ESTRUCTURAS DE DATOS 6
4.4. OPERACIÓN DE MODIFICACIÓN
Para modificar el elemento que se encuentra en cierta posición de la lista, basta acceder al
arreglo en la posición dada y cambiar el valor.
Código 13: Método para modificar el valor del elemento que está en determinada posición de la lista.
public E set(int index, E element) {
// Obtener el elemento que se encuentra en la posición index:
E anteriorValor=get(index);
// Modificar el elemento de la posición index con el valor element:
arreglo[index]=element;
// Retornar el elemento que anteriormente se encontraba en la posición dada:
return anteriorValor;
}
Tabla 14: Análisis de complejidad temporal del método E set(int index, E element).
Tipo de análisis Complejidad Justificación
Peor caso
Acceder la posición de un arreglo para modificarla tiene
Caso promedio
complejidad temporal .
Mejor caso
Antes de insertar un elemento en la estructura de datos hay que revisar si cabe en el arreglo.
En caso de que el arreglo esté completamente lleno (lo que sucede cuando se agotan las
casillas de reserva), es necesario incrementar su capacidad a través del siguiente proceso:
Código 15: Método para asegurar que cierta cantidad de valores quepa en el arreglo.
private void garantizarCapacidad(int nuevaCantidadDeElementos) {
// Si la nueva cantidad de elementos es menor o igual que la capacidad del
// arreglo:
if (nuevaCantidadDeElementos<=arreglo.length) {
// No hacer nada porque los elementos ya caben.
}
// Si la nueva cantidad de elementos es mayor o igual que la capacidad del
// arreglo, tocaría "crecer" la capacidad del arreglo.
else {
// Si la nueva cantidad de elementos es menor que el doble de la capacidad
// del arreglo, dejarla en el doble de la capacidad del arreglo:
ESTRUCTURAS DE DATOS 7
if (nuevaCantidadDeElementos<arreglo.length*2) {
nuevaCantidadDeElementos=arreglo.length*2;
}
// Crear un nuevo arreglo donde quepa la nueva cantidad de elementos:
Object[] nuevoArreglo=new Object[nuevaCantidadDeElementos];
// Copiar todos los elementos del viejo arreglo al nuevo arreglo:
for (int i=0; i<tamanho; i++) {
nuevoArreglo[i]=arreglo[i];
}
// Desechar el viejo arreglo, asignándole el nuevo:
arreglo=nuevoArreglo;
}
}
Como el proceso que incrementa la capacidad del arreglo es ineficiente, pues su complejidad
temporal es , hay que evitar a toda costa que sea ejecutado frecuentemente. Un
mecanismo para reducir sustancialmente el número de veces que se debe crecer el arreglo es
duplicar su capacidad cada vez que se necesite crecer.
Es posible alterar la capacidad del arreglo con un porcentaje distinto al 200% (que
corresponde a duplicar el número de casillas). Por ejemplo, la clase ArrayList<E> de Java
modifica la capacidad en un 150% cada vez que se necesite hacer crecer el arreglo.
Ahora pensemos en el proceso de inserción como tal, sabiendo que contamos con un
mecanismo que es capaz de asegurarnos el espacio para alojar el nuevo elemento. Para
insertar el valor element en la posición index de la lista:
†
Sexagésimo cuarto es el número ordinal para representar el elemento número 64.
ESTRUCTURAS DE DATOS 8
2. Se corren una posición hacia la derecha todos los elementos de la lista que están ubicados
desde la posición index hasta la posición tamanho-1, recorriendo el arreglo al revés: primero
se traslada el elemento de la posición tamanho-1, luego se traslada el elemento de la posición
tamanho-2, …, y finalmente se mueve el elemento de la posición index. ¿Qué tan complicada
queda la programación si hacemos el corrimiento recorriendo el arreglo al derecho en lugar
de hacerlo al revés?
3. Se ubica el valor element en la posición index de la lista.
4. Se incrementa el tamaño de la lista (tamanho) en una unidad.
Gráfica 16: Paso 2 del proceso que inserta un elemento en la posición k (donde k=index).
0 1 2 k-1 k k+1 k+2 n-1 n n+1 t-1
... ... ...
... ...
Tabla 18: Análisis de complejidad temporal del método void add(int index, E element).
Tipo de análisis Complejidad Justificación
El peor caso ocurre en dos situaciones:
Cuando la inserción es al principio: la complejidad temporal es
pues todos los elementos de la lista se deben correr una
posición a la derecha.
Peor caso
Cuando se debe crecer la capacidad del arreglo: la complejidad
temporal es pues se debe crear el nuevo arreglo, trasladar
todos los elementos a ese nuevo arreglo, y desarrollar la
inserción.
En el caso promedio la inserción ocurre cerca de la mitad de la
lista. La complejidad temporal en esta situación es porque
Caso promedio
hay que trasladar aproximadamente elementos una posición
a la derecha.
ESTRUCTURAS DE DATOS 9
El mejor caso ocurre cuando hay casillas de reserva y la inserción
se realiza al final de la lista. La complejidad temporal en esta
Mejor caso
situación es porque no hay que trasladar ningún elemento
ni hay que crecer el arreglo.
Gráfica 19: Pasos 2 y 3 del proceso que elimina el elemento de la posición k (donde k=index).
0 1 2 k-1 k k+1 k+2 n-2 n-1 n t-1
... ... ...
... ... Paso 2
Código 20: Método para eliminar el elemento presente en determinada posición de la lista.
public E remove(int index) {
// Obtener el elemento que se encuentra en la posición index:
E elemento=get(index);
// Correr una posición hacia la izquierda todos los elementos desde la
// posición index+1 hasta la posición tamanho-1:
for (int i=index+1; i<tamanho; i++) {
arreglo[i-1]=arreglo[i];
}
// Poner el valor null en la posición tamanho-1 del arreglo:
arreglo[tamanho-1]=null;
// Decrecer el tamaño en una unidad:
tamanho--;
// Retornar el elemento que se antes se encontraba en la posición index:
ESTRUCTURAS DE DATOS 10
return elemento;
}
EN RESUMEN
En Java, toda variable de tipo básico se administra por valor, es decir, se trata como un registro que
almacena un valor. Por otro lado, toda variable cuyo tipo sea una clase se administra por referencia,
es decir, se trata como un apuntador que referencia a un objeto, almacenando la dirección en
memoria donde éste se encuentra.
La constante null, que representaremos con el símbolo , actúa en Java como un apuntador a la
nada.
El Recolector de Basura (en inglés: Garbage Collector) libera la memoria ocupada por aquellos objetos
que se han dejado de referenciar en nuestro programa.
Cada vez que necesitemos una lista, deberemos decidir si escoger la implementación con vectores
(ArrayList<E>) o la implementación con nodos encadenados (LinkedList<E>), dependiendo de cuál
nos dé la mejor eficiencia en el problema que estemos resolviendo.
La clase VEDArrayList<E> implementa listas con arreglos dinámicos de tamaño variable (vectores).
Ventajas de la implementación de listas con vectores (VEDArrayList<E>):
Todos los elementos están contiguos en memoria principal.
No requiere tanta memoria principal puesto que no hay encadenamientos.
Las operaciones de consulta por posición y modificación por posición son muy eficientes: .
Las inserciones al final de la lista son eficientes cuando no toca crecer la capacidad del arreglo: .
Las eliminaciones al final de la lista siempre son eficientes .
Desventajas de la implementación de listas con vectores (VEDArrayList<E>):
Se debe ejecutar una operación costosa cada vez que se llene la capacidad del arreglo (no importa
mucho porque no sucede muy frecuentemente).
Las inserciones al principio de la lista son ineficientes: .
Las eliminaciones al principio de la lista son ineficientes: .
ESTRUCTURAS DE DATOS 11
PARA TENER EN CUENTA
La clase VEDArrayList<E> nos sirvió para conocer con detalle la estructura interna de las clases
ArrayList<E> y Vector<E> de Java. De ahora en adelante, usaremos ArrayList<E> y Vector<E> en
vez de VEDArrayList<E> porque están disponibles en la librería estándar de Java, nos ofrecen
muchos más servicios, y son capaces de lanzar error cada vez que el usuario intente realizar alguna
operación inválida, como acceder a una posición que esté por fuera de la lista.
ESTRUCTURAS DE DATOS 12
ESTRUCTURAS DE DATOS
UNIDAD DOS - SEMANA CUATRO
IMPLEMENTACIONES DE LISTAS (PARTE 2) *
TABLA DE CONTENIDO
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. IMPLEMENTACIÓN CON LISTAS DOBLEMENTE ENCADENADAS
EN ANILLO CON ENCABEZADO
Recurso como proyecto en Eclipse: ImplementacionesListas.zip.
En esta estructura de datos, los nodos son capaces de almacenar valores y de apuntar al
nodo anterior y al nodo siguiente en el encadenamiento. Un nodo, representado con la clase
VEDNodo<E>, tiene los siguientes atributos:
sig
val
ant
Cada elemento de la lista se almacena en un nodo por separado. Además hay un nodo
especial, llamado encabezado o header, cuyo valor es null y que cumple como propósito
hacer más cómoda la programación de la estructura de datos.
header
tamanho
...
...
ESTRUCTURAS DE DATOS 2
Gráfica 3: Representación lineal de la lista como
una lista doblemente encadenada en anillo con encabezado.
VEDLinkedList<E>
header ...
tamanho
Gráfica 4: Representación de la lista vacía como una lista doblemente encadenada en anillo con encabezado.
VEDLinkedList<E>
header
tamanho
tamanho: es una variable de tipo int que almacena el tamaño de la lista, y que abreviaremos con la
letra . Recuerde que el tamaño de una lista es el número de elementos que almacena.
header: es un nodo especial llamado el encabezado de la lista. El propósito de su existencia es
facilitar la labor de programar la estructura de datos. Siempre tiene valor null.
Nunca olvide que el encabezado no guarda ningún elemento de la lista. Por esta razón, si el tamaño
de la lista es , entonces habrán nodos, contando el encabezado.
// **********************************
// * Clase que representa los nodos *
// **********************************
private static class VEDNodo<E> {
E val=null; // Valor del nodo
VEDNodo<E> ant=null; // Nodo anterior
VEDNodo<E> sig=null; // Nodo siguiente
VEDNodo(E pVal) { // Constructor de la clase nodo
val=pVal;
}
}
// *************************
// * Atributos de la clase *
// *************************
/**
* Encabezado. Es declarado "final" para que no sea reasignado.
*/
private final VEDNodo<E> header=new VEDNodo<E>(null);
/**
* Tamaño de la lista (inicia en 0).
*/
private int tamanho=0;
ESTRUCTURAS DE DATOS 3
// ***********************
// * Métodos de la clase *
// ***********************
// ...
1.1. CONSTRUCTOR
El método constructor de la clase, que se responsabiliza de crear una nueva lista vacía, debe
asegurar que se cumpla la situación:
Gráfica 7: Estado de la lista justo después de la inicialización de los atributos de la clase VEDLinkedList<E>.
VEDLinkedList<E>
header
tamanho
¿Falta algo? ¡Poner como anterior y como siguiente del encabezado al mismo encabezado!
ESTRUCTURAS DE DATOS 4
1.2. DESTRUCTOR
Para eliminar todos los elementos de la lista existe el método clear, cuya implementación
debe asegurar que se pase de la situación:
a la situación:
Basta con poner como anterior y como siguiente del encabezado al mismo encabezado y
asignarle el valor a la variable tamanho.
Gráfica 12: Estado de la lista después de reencadenar el encabezado consigo mismo y de asignarle cero al tamaño.
VEDLinkedList<E>
header ...
tamanho
¿Qué pasa con los nodos de la lista que están coloreados de verde? Debido a que no hay
forma de llegar a ellos, se convierten en basura y la memoria que ocupan sería liberada por el
Recolector de Basura cuando sea el momento. Concluimos entonces que después de las
operaciones descritas, eliminamos todos los elementos de la lista, dejándola vacía.
ESTRUCTURAS DE DATOS 5
Caso promedio temporal es , pero considerándolo, la complejidad sería
Mejor caso .
Código 15: Método que retorna el nodo que almacena el valor que se encuentra en determinada posición de la lista.
private VEDNodo<E> getNodo(int index) {
VEDNodo<E> x=header;
if (index<tamanho/2) {
for (int i=0; i<=index; i++) {
x=x.sig;
}
}
else {
for (int i=tamanho-1; i>=index; i--) {
x=x.ant;
}
}
return x;
}
Si index está entre 0 y tamanho-1, el método getNodo nos retorna como resultado el nodo que
almacena el elemento de la posición index de la lista; de lo contrario, nos retorna header. En
caso de que index sea menor que la mitad del tamaño de la lista, sale más barato llegar al
nodo yendo hacia delante de siguiente en siguiente; por otro lado, si index es mayor que la
mitad del tamaño de la lista, sale más barato llegar al nodo yendo hacia atrás de anterior en
anterior. Observe que si index es igual a la mitad del tamaño de la lista, es igual de pesado ir
hacia delante que ir hacia atrás (¿Por qué?)
Tabla 16: Análisis de complejidad temporal del método VEDNodo<E> getNodo(int index).
Tipo de análisis Complejidad Justificación
El peor caso ocurre cuando index está cerca de la mitad de la
lista, dando una complejidad temporal de porque hay que
Peor caso
pasar aproximadamente por la mitad de los nodos para llegar al
nodo que deseamos.
En el caso promedio, index está cerca de la mitad de la lista. La
Caso promedio
complejidad temporal es por la razón dada en el peor caso.
El mejor caso ocurre cuando index es 0 o tamanho-1. Si index es
0, el nodo requerido es el siguiente del header, y si index es
Mejor caso
tamanho-1, el nodo requerido es el anterior del header. En
ambos casos, la complejidad es .
ESTRUCTURAS DE DATOS 6
Usando el método getNodo podemos consultar el elemento que se encuentra en cierta
posición de la lista.
Código 17: Método para consultar el valor del elemento que está en determinada posición de la lista.
public E get(int index) {
// Obtener el nodo que guarda el elemento que está en la posición index:
VEDNodo<E> nodo=getNodo(index);
// Retornar el valor almacenado en el nodo:
return nodo.val;
}
Note que la complejidad temporal del algoritmo depende completamente del método
getNodo.
Código 19: Método para modificar el valor del elemento que está en determinada posición de la lista.
public E set(int index, E element) {
// Obtener el nodo que guarda el elemento que está en la posición index:
VEDNodo<E> nodo=getNodo(index);
// Recuperar el valor almacenado en el nodo:
E anteriorValor=nodo.val;
// Cambiar el valor almacenado en el nodo por el valor element:
nodo.val=element;
// Retornar el elemento que anteriormente se encontraba en la posición dada:
return anteriorValor;
}
Observe que la complejidad temporal del algoritmo depende completamente del método
getNodo.
Tabla 20: Análisis de complejidad temporal del método E set(int index, E element).
Tipo de análisis Complejidad Justificación
Modificar el elemento de la mitad de la lista tiene complejidad
Peor caso
.
Caso promedio Modificar elementos cerca de la mitad de la lista tiene
ESTRUCTURAS DE DATOS 7
complejidad .
Modificar el primer o el último elemento de la lista tiene
Mejor caso
complejidad .
1. Creamos un nuevo nodo cuyo valor sea el elemento que queremos insertar.
VEDNodo<E> nuevo=new VEDNodo<E>(e);
VEDLinkedList<E>
header ... ...
tamanho
nuevo
nuevo
3. Ponga el anterior del nodo nuevo en el nodo a y el siguiente del nodo nuevo en el nodo b.
nuevo.ant=a;
nuevo.sig=b;
VEDLinkedList<E> a b
nuevo
4. Ponga el siguiente del nodo a en el nodo nuevo y el anterior del nodo b en el nodo nuevo.
ESTRUCTURAS DE DATOS 8
a.sig=nuevo;
b.ant=nuevo;
VEDLinkedList<E> a b
nuevo
Observe que la complejidad temporal del algoritmo depende completamente del método
getNodo.
Tabla 22: Análisis de complejidad temporal del método void add(int index, E element).
Tipo de análisis Complejidad Justificación
Peor caso Insertar un valor en la mitad de la lista tiene complejidad .
Insertar valores cerca de la mitad de la lista tiene complejidad
Caso promedio
.
Insertar un valor al principio o al final de la lista tiene
Mejor caso
complejidad .
¿Por qué el algoritmo de inserción descrito funciona para insertar al principio de la lista, para
insertar al final de la lista y para insertar en medio de la lista? ¿Tiene algo que ver con el
encabezado?
Usando el método getNodo también podemos eliminar el elemento ubicado en cierta posición
de la lista (suponga que k es la posición del elemento que queremos eliminar de la lista):
ESTRUCTURAS DE DATOS 9
1. Con la ayuda de la función getNodo, obtenemos un apuntador b al nodo que almacena el
elemento de la posición index, declaramos a como un apuntador al nodo anterior del nodo b,
y declaramos c como un apuntador al nodo siguiente del nodo b.
VEDNodo<E> b=getNodo(index);
VEDNodo<E> a=b.ant;
VEDNodo<E> c=b.sig;
VEDLinkedList<E> a c
4. Como el elemento dejó de ser referenciado por la lista, se volvió basura. Cuando lo
decida la Máquina Virtual de Java, el Recolector de Basura liberará la memoria ocupada por
este nodo.
VEDLinkedList<E>
header ... ...
tamanho
Código 23: Método para eliminar el elemento presente en determinada posición de la lista.
public E remove(int index) {
// Obtener el nodo que guarda el elemento que está en la posición index:
VEDNodo<E> b=getNodo(index);
// Obtener el anterior del nodo b:
VEDNodo<E> a=b.ant;
// Obtener el siguiente del nodo b:
VEDNodo<E> c=b.sig;
// Reencadenar los nodos de la lista:
a.sig=c;
c.ant=a;
// Decrecer el tamaño en una unidad:
tamanho--;
// Retornar el elemento que se antes se encontraba en la posición index:
return b.val;
}
ESTRUCTURAS DE DATOS 10
Observe que la complejidad temporal del algoritmo depende completamente del método
getNodo.
¿Por qué el algoritmo de eliminación descrito funciona para eliminar del principio de la lista,
para eliminar del final de la lista y para eliminar de en medio de la lista? ¿Tiene algo que ver
con el encabezado?
Gráfica 26: Representación de la lista vacía como una lista doblemente encadenada de tipo VEDLinkedListD<E>.
VEDLinkedListD<E>
primero
ultimo
tamanho
ESTRUCTURAS DE DATOS 11
3. IMPLEMENTACIÓN CON LISTAS SENCILLAMENTE
ENCADENADAS
Las listas también se pueden implementar con nodos sencillamente encadenados con
apuntador al primero (para futura referencia, llamaremos a esta versión VEDLinkedListS<E>).
En una lista sencillamente encadenada los nodos sólo tienen valor y apuntador al siguiente.
Gráfica 27: Representación gráfica de los nodos en una lista sencillamente encadenada.
sig
val
Gráfica 29: Representación de la lista vacía como una lista sencillamente encadenada de tipo VEDLinkedListS<E>.
VEDLinkedListS<E>
primero
tamanho
ESTRUCTURAS DE DATOS 12
VEDLinkedList Las operaciones de consulta, Las operaciones de consulta,
modificación, inserción y eliminación modificación, inserción y eliminación
son muy eficientes tanto al principio son ineficientes cerca de la mitad de la
como al final de la lista: . lista: .
Es fácil de implementar debido a la Las consultas sólo son eficientes al
presencia de encabezado y a la principio y al final de la lista.
ausencia de apuntadores nulos. Requiere más memoria principal que
VEDArrayList<E> puesto que se deben
mantener los encadenamientos.
VEDLinkedListD Las operaciones de consulta, Las operaciones de consulta,
modificación, inserción y eliminación modificación, inserción y eliminación
son muy eficientes tanto al principio son ineficientes cerca de la mitad de la
como al final de la lista: . lista: .
Las consultas sólo son eficientes al
principio y al final de la lista.
Requiere más memoria principal que
VEDArrayList<E> puesto que se deben
mantener los encadenamientos.
Es difícil de implementar debido a la
ausencia de encabezado y a la
presencia de apuntadores nulos.
VEDLinkedListS Las operaciones de consulta, Las operaciones de consulta,
modificación, inserción y eliminación modificación, inserción y eliminación
son muy eficientes al principio de la son ineficientes en toda posición de la
lista: . lista excepto al principio: .
Es difícil de implementar debido a la
ausencia de encabezado y a la
presencia de apuntadores nulos.
Tabla 31: Algunos criterios importantes para decidir cuál estructura de datos de Java escoger para representar listas.
Si desea … Prefiera usar …
usar poca memoria principal ArrayList<E>
eficiencia consultando el elemento de cualquier posición ArrayList<E>
eficiencia modificando el elemento de cualquier posición ArrayList<E>
eficiencia en las operaciones básicas sólo sobre el final de la lista ArrayList<E>
eficiencia en las operaciones básicas tanto al principio como al final de la lista LinkedList<E>
eficiencia en las operaciones básicas sólo sobre el principio de la lista LinkedList<E>
crear una lista tan grande que no sea posible reservar en memoria un arreglo
LinkedList<E>
del tamaño suficiente para alojar sus elementos
EN RESUMEN
Cada vez que necesitemos una lista, deberemos decidir si escoger la implementación con vectores
(ArrayList<E>) o la implementación con nodos encadenados (LinkedList<E>), dependiendo de cuál
nos dé la mejor eficiencia en el problema que estemos resolviendo.
ESTRUCTURAS DE DATOS 13
La clase ArrayList<E> implementa listas con arreglos dinámicos de tamaño variable (vectores).
Ventajas de la implementación de listas con vectores (ArrayList<E>):
Todos los elementos están contiguos en memoria principal.
No requiere tanta memoria principal puesto que no hay encadenamientos.
Las operaciones de consulta por posición y modificación por posición son muy eficientes: .
Las inserciones al final de la lista son eficientes cuando no toca crecer la capacidad del arreglo: .
Las eliminaciones al final de la lista siempre son eficientes .
Desventajas de la implementación de listas con vectores (ArrayList<E>):
Se debe ejecutar una operación costosa cada vez que se llene la capacidad del arreglo (no importa
mucho porque no sucede muy frecuentemente).
Las inserciones al principio de la lista son ineficientes: .
Las eliminaciones al principio de la lista son ineficientes: .
La clase LinkedList<E> implementa listas con nodos doblemente encadenados en anillo con
encabezado.
Ventajas de la implementación de listas con nodos doblemente encadenados (LinkedList<E>):
Las operaciones de consulta, modificación, inserción y eliminación son muy eficientes tanto al
principio como al final de la lista: .
Es fácil de implementar debido a la presencia de encabezado y a la ausencia de apuntadores nulos.
Desventajas de la implementación de listas con nodos doblemente encadenados (LinkedList<E>):
Las operaciones de consulta, modificación, inserción y eliminación son ineficientes cerca de la mitad
de la lista: .
Las consultas sólo son eficientes al principio y al final de la lista.
Requiere más memoria principal que ArrayList<E> puesto que se deben mantener los
encadenamientos.
ESTRUCTURAS DE DATOS 14
ESTRUCTURAS DE DATOS
UNIDAD DOS - SEMANA CUATRO
ITERADORES SOBRE LISTAS *
TABLA DE CONTENIDO
1. DEFINICIÓN 2
2. RECORRIDO DE UNA LISTA USANDO EL MÉTODO GET 2
3. RECORRIDO DE UNA LISTA USANDO ITERADORES 4
4. RECORRIDO DE UNA LISTA USANDO LA INSTRUCCIÓN FOR-EACH 5
5. ELIMINACIÓN DE ELEMENTOS DE UNA LISTA USANDO ITERADORES 6
EN RESUMEN 6
PARA TENER EN CUENTA 6
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. DEFINICIÓN
Considerando una colección como una agrupación de elementos del mismo tipo, se puede
definir un iterador como un objeto responsable de visitar los objetos de una colección en
cierto orden preestablecido, sin que el programador tenga que preocuparse por su
estructura interna. La interfaz Iterator<E> representa en Java un iterador de elementos de
tipo E, ofreciéndonos los siguientes servicios:
Mientras se está desarrollando una iteración sobre una colección, se debe evitar modificar el
contenido de ésta con métodos distintos al remove de la interfaz Iterator<E>, para no
obtener resultados inesperados como errores de ejecución o pérdida de datos.
Lo más natural que muchos nos imaginamos para lograr pasar por todos los elementos de
una lista es un ciclo que inspeccione cada una de sus posiciones, desde la posición hasta la
posición , donde es el tamaño de la lista:
El gran problema de esta forma de recorrer es que es muy ineficiente si la lista está
implementada con nodos encadenados. Habíamos estudiado que el método get, con el que
consultamos el elemento que está en cierta posición de la lista, tiene complejidad temporal
en la implementación de listas con vectores y tiene complejidad temporal en la
implementación de listas con nodos encadenados. Como el ciclo for (int i=0; i<n; i++)
realiza exactamente iteraciones, la complejidad temporal del anterior fragmento de código
sería multiplicado por la complejidad del método get. Por esta razón, si la lista está
implementada con nodos encadenados, el recorrido sobre la lista que usa reiterativamente el
ESTRUCTURAS DE DATOS 2
método get tendría complejidad temporal , lo que es completamente inaceptable si la
lista es demasiado grande. Para convencernos de esto, considere la siguiente prueba:
Código 3: Programa que mide el tiempo que se demora el recorrido de una lista encadenada usando el método get.
import java.util.*;
public class Ejemplo {
public static void main(String[] args) {
for (int n=10000; n<=100000; n+=10000) {
List<String> lista=new LinkedList<String>();
for (int i=0; i<n; i++) { // Llenar la lista con n elementos cualesquiera
lista.add("");
}
long tiempo1=System.currentTimeMillis();
for (int i=0; i<n; i++) { // Realizar el recorrido de forma ineficiente
String elementoActual=lista.get(i);
}
long tiempo2=System.currentTimeMillis();
long demora=tiempo2-tiempo1;
System.out.println("Demora iterando una lista encadenada de tamaño "+n+": "+
demora+" milisegundos");
System.gc(); // Invocar al recolector de basura
}
}
}
Gráfica 4: Tamaño de la lista encadenada ( ) versus el tiempo consumido para recorrerla, usando el método get.
Observe que la gráfica que relaciona el tamaño de la lista con el consumo de tiempo refleja
una función cuadrática, lo que nos confirma que el recorrido de una lista encadenada usando
el método get tiene complejidad temporal . Cambiando LinkedList<String> por
ArrayList<String> obtenemos tiempos mucho menores que exhiben una tendencia lineal
( ) y no cuadrática ( ), incluso si aumentamos la cantidad de datos de la muestra de
cien mil a diez millones.
ESTRUCTURAS DE DATOS 3
Gráfica 5: Tamaño de la lista implementada con vectores ( ) versus el tiempo
consumido para recorrerla, usando el método get.
La forma más adecuada y eficiente de recorrer una lista es a través de un iterador, que nos
ofrece un mecanismo para pasar ordenadamente por todos sus elementos, con complejidad
temporal , sin importar si la lista está implementada con vectores o con nodos
encadenados.
ESTRUCTURAS DE DATOS 4
Tabla 8: Ejemplos sobre recorridos de listas usando iteradores.
Ejemplo Descripción
List<Integer> lista=new
ArrayList<Integer>();
lista.addAll(Arrays.asList(17,25,19,20));
Iterator<Integer>
iterador=lista.iterator();
Imprime en consola los elementos de la lista
while (iterador.hasNext()) {
Integer elementoActual=iterador.next();
System.out.println(elementoActual);
}
List<Integer> lista=new
ArrayList<Integer>();
lista.addAll(Arrays.asList(17,25,19,20));
int r=0;
Iterator<Integer> Imprime en consola el resultado de la suma de
iterador=lista.iterator(); los elementos de la lista que es
while (iterador.hasNext()) {
exactamente .
Integer elementoActual=iterador.next();
r+=elementoActual;
}
System.out.println(r);
En Java, la iteración
Iterator<E> iterador=lista.iterator();
while (iterador.hasNext()) {
E elementoActual=iterador.next();
// Procesar el elemento actual como sea conveniente:
// ...
}
se puede abreviar por medio de una instrucción denominada for-each:
for (E elementoActual:lista) {
// Procesar el elemento actual como sea conveniente:
// ...
}
ESTRUCTURAS DE DATOS 5
5. ELIMINACIÓN DE ELEMENTOS DE UNA LISTA USANDO
ITERADORES
Usando el método remove() de la interfaz Iterator<E> se pueden eliminar de una lista los
elementos que cumplen cierta condición dada.
Código 10: Remoción de los valores de una lista que cumplen determinada condición.
Iterator<E> iterador=lista.iterator();
while (iterador.hasNext()) {
E elementoActual=iterador.next();
if (condición) iterador.remove();
}
Tabla 11: Ejemplos sobre eliminación de elementos en las listas usando iteradores.
Ejemplo Descripción
public static void f01(List<Integer>
lista) {
Iterator<Integer> it=lista.iterator();
while (it.hasNext()) { Función que elimina de la lista dada todos los
int x=it.next(); elementos cuyo valor sea .
if (x==5) it.remove();
}
}
public static void f02(List<Integer>
lista) {
Iterator<Integer> it=lista.iterator();
while (it.hasNext()) { Función que elimina de la lista dada todos los
int x=it.next(); elementos cuyo valor esté entre y .
if (x>=5&&x<=8) it.remove();
}
}
EN RESUMEN
Los iteradores nos proveen una forma eficiente de visitar todos los elementos de una lista.
ESTRUCTURAS DE DATOS 6
ESTRUCTURAS DE DATOS
UNIDAD DOS - SEMANA CUATRO
OTRAS ESTRUCTURAS DE DATOS LINEALES *
TABLA DE CONTENIDO
1. LISTAS ORDENADAS 2
2. PILAS 2
3. COLAS 4
4. COLAS DE PRIORIDAD 6
5. APLICACIONES 9
5.1. EVALUACIÓN DE EXPRESIONES EN NOTACIÓN INFIJA 9
5.2. EVALUACIÓN DE EXPRESIONES EN NOTACIÓN POSFIJA (NOTACIÓN POLACA INVERSA) 10
EN RESUMEN 11
PARA TENER EN CUENTA 11
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
1. LISTAS ORDENADAS
Una lista ordenada es una lista sin elementos repetidos que se almacenan de menor a mayor
según cierto criterio de ordenamiento.
Ejemplos:
es una lista ordenada de enteros.
es una lista ordenada de cadenas de texto según el orden del
diccionario.
es una lista ordenada de cadenas de texto que representan
números romanos, según el orden establecido por la numeración romana (I, II, III, IV, V, VI,
VII, VIII, IX, X, …)
Para mantener ordenada una lista es necesario modificar las operaciones, garantizando que
no se inserten elementos repetidos y que siempre queden almacenados los elementos en
orden. Recuerde que, con el algoritmo de Búsqueda Binaria seríamos capaces de encontrar
un valor en una lista ordenada de tamaño , con una complejidad temporal de .
Se aconseja almacenar las listas ordenadas en un arreglo dinámico de tamaño variable en vez
de en una lista encadenada, porque la Búsqueda Binaria requiere que las consultas por
posición sean para que pueda ofrecernos la complejidad temporal de .
2. PILAS
Una pila (en inglés: stack) es una estructura de datos lineal que sólo permite inserciones y
eliminaciones en uno de los extremos, llamado tope. La clase Stack<E> de Java, que extiende
de la clase Vector<E>, representa una pila de elementos de tipo E, brindándonos las
siguientes operaciones:
ESTRUCTURAS DE DATOS 2
†
Gráfica 2: Ejemplos de pilas en la vida real .
Pila de discos en el
Pila de libros Pila de monedas Pila de piedras
juego de Hanoi
Como todo elemento en una pila puede ser añadido o eliminado sólo por el tope, se cumple
que el último elemento que se inserta es el primer elemento que puede eliminarse, razón
por la que se dice que esta estructura de datos satisface que el último que entra es el primero
que sale (Last-in First-Out en inglés, abreviado con las siglas LIFO).
tope
...
Gracias a que la clase Stack<E> extiende de la clase Vector<E>, las pilas en Java están
implementadas con arreglos dinámicos de tamaño variable (vectores).
Gráfica 4: Representación interna de las pilas en Java, a través de arreglos dinámicos de tamaño variable.
Stack<E>
extiende
Vector<E>
0 1 2 n-1 n t-1
arreglo ... ...
tamanho
casillas ocupadas casillas de reserva
tope
Estando el tope en la última posición del vector, las operaciones push, pop y peek tienen
complejidad temporal , porque las inserciones, eliminaciones y consultas al final de una
†
La segunda y tercera imagen se descargaron de http://www.public-domain-photos.com/ y la cuarta ilustración
pertenece a la galería de imágenes prediseñadas de Microsoft Office. JFreeChart Samples, © 2005-2009 Object
Refinery Limited (recuperado en noviembre de 2010).
ESTRUCTURAS DE DATOS 3
lista implementada con arreglos son todas, con la salvedad de que la complejidad de las
inserciones se vuelve cuando hay que crecer el tamaño del arreglo.
Tabla 5: Evolución de una pila de cadenas de texto tras una secuencia de operaciones de ejemplo.
Estado de la pila Operación Descripción
Stack<String> pila
=new Stack<String>(); Crear una pila vacía.
pila.push("Luz"); Agregar Luz en el tope de la pila.
pila.push("Mar"); Agregar Mar en el tope de la pila.
pila.push("Sol"); Agregar Sol en el tope de la pila.
pila.pop(); Eliminar el tope de la pila.
pila.push("Mes"); Agregar Mes en el tope de la pila.
pila.push("Año"); Agregar Año en el tope de la pila.
pila.pop(); Eliminar el tope de la pila.
pila.pop(); Eliminar el tope de la pila.
pila.pop(); Eliminar el tope de la pila.
pila.push("Día"); Agregar Día en el tope de la pila.
pila.push("Luz"); Agregar Luz en el tope de la pila.
pila.pop(); Eliminar el tope de la pila.
pila.pop(); Eliminar el tope de la pila.
pila.pop(); Eliminar el tope de la pila.
3. COLAS
Una cola (en inglés: queue, pronunciado kiu) es una estructura de datos lineal que permite
inserciones en un extremo (llamado cola) y eliminaciones del otro extremo (llamado cabeza).
La interfaz Queue<E> de Java, implementada por la clase LinkedList<E>, representa una cola
de elementos de tipo E, brindándonos las siguientes operaciones:
ESTRUCTURAS DE DATOS 4
‡
Gráfica 7: Cola de personas .
Como todo elemento en una cola puede ser insertado sólo en la cola y eliminado sólo de la
cabeza, se cumple que el primer elemento que se inserta es el primer elemento que puede
eliminarse, razón por la que se dice que esta estructura de datos satisface que el primero que
entra es el primero que sale (First-in First-Out en inglés, abreviado con las siglas FIFO).
...
eliminación
(poll)
Gráfica 9: Representación interna de las colas en Java, a través de nodos doblemente encadenados.
Queue<E>
implementa
LinkedList<E>
header ...
tamanho
cabeza cola
‡
La ilustración pertenece a la galería de imágenes prediseñadas de Microsoft Office.
ESTRUCTURAS DE DATOS 5
Estando la cola en la última posición de la lista y la cabeza en la primera, las operaciones
offer, poll y peek tienen complejidad temporal , porque las inserciones, eliminaciones y
consultas tanto al principio como al final de una lista implementada con nodos doblemente
encadenados son todas. Observe que no sería muy apropiado implementar colas con
arreglos dinámicos de tamaño variable de tipo ArrayList<E> porque las inserciones y
eliminaciones al principio del arreglo tienen complejidad .
Tabla 10: Evolución de una cola de cadenas de texto tras una secuencia de operaciones de ejemplo.
Estado de la cola Operación Descripción
Queue<String> cola
=new LinkedList<String>(); Crear una cola vacía.
cola.offer("Luz"); Agregar Luz en la cola.
cola.offer("Mar"); Agregar Mar en la cola.
cola.offer("Sol"); Agregar Sol en la cola.
cola.poll(); Eliminar la cabeza de la cola.
cola.offer("Mes"); Agregar Mes en la cola.
cola.offer("Año"); Agregar Año en la cola.
cola.poll(); Eliminar la cabeza de la cola.
cola.poll(); Eliminar la cabeza de la cola.
cola.poll(); Eliminar la cabeza de la cola.
cola.offer("Día"); Agregar Día en la cola.
cola.offer("Luz"); Agregar Luz en la cola.
cola.poll(); Eliminar la cabeza de la cola.
cola.poll(); Eliminar la cabeza de la cola.
cola.poll(); Eliminar la cabeza de la cola.
4. COLAS DE PRIORIDAD
Recurso como proyecto en Eclipse: ColasDePrioridad.zip.
Una cola de prioridad es una cola donde los elementos son eliminados según el orden
establecido por cierto criterio de prioridad. La clase PriorityQueue<E> de Java, que
implementa la interfaz Queue<E>, representa una cola de prioridad de elementos de tipo E,
brindándonos las siguientes operaciones:
Tabla 11: Algunos métodos de la clase PriorityQueue<E>, que implementa la interfaz Queue<E>.
Método Descripción
boolean offer(E item)
Inserta en la cola el ítem suministrado, con la prioridad que éste
tenga definida. Retorna verdadero en caso de éxito.
Elimina y retorna el objeto de la cola que tiene la mayor prioridad
E poll()
para ser atendido entre todos los elementos.
E peek()
Retorna el objeto de la cola que tiene la mayor prioridad para ser
atendido entre todos los elementos.
ESTRUCTURAS DE DATOS 6
En la clase PriorityQueue<E>, los métodos offer y poll tienen complejidad , y el
método peek tiene complejidad . Internamente, PriorityQueue administra los elementos
almacenándolos en un arreglo de una forma tal que se garantice eficiencia al momento de
insertar y eliminar elementos de la cola (específicamente es un priority heap, que no
trataremos en las lecturas).
Para conocer la utilidad de esta estructura de datos, considere el siguiente ejemplo: todos
sabemos que los enfermos en un hospital deben ser atendidos según la prioridad que
representen sus heridas; por ejemplo, un herido de muerte tiene más prioridad que una
persona con dolor de cabeza. La siguiente clase modela los enfermos del hospital:
El atributo nombre almacena los nombres y apellidos del enfermo, y el atributo prioridad es
un puntaje que indica qué tanta urgencia tiene el enfermo para ser atendido, donde mayor
puntaje significa mayor prioridad. Para que una cola de prioridad sepa que la persona con
mayor prioridad entre todas es la que primero debe ser atendida, es necesario que la clase
Enfermo implemente la interfaz Comparable<Enfermo>.
Código 13: Clase para representar enfermos, que implementa la interfaz Comparable.
public class Enfermo implements Comparable<Enfermo> {
private String nombre;
private int prioridad;
public Enfermo(String pNombre, int pPrioridad) {
nombre=pNombre;
prioridad=pPrioridad;
}
public String getNombre() {
return nombre;
}
public int getPrioridad() {
return prioridad;
}
public int compareTo(Enfermo pEnfermo) {
Enfermo f=this,g=pEnfermo;
if (f.getPrioridad()>g.getPrioridad()) {
return -1;
}
else if (f.getPrioridad()<g.getPrioridad()) {
return 1;
ESTRUCTURAS DE DATOS 7
}
else {
return 0;
}
}
}
Código 14: Ejemplo que ilustra el uso de las colas de prioridad para atender elementos según su urgencia.
import java.util.*;
public class Hospital {
public static void main(String[] args) {
PriorityQueue<Enfermo> c=new PriorityQueue<Enfermo>();
c.add(new Enfermo("Juan Pérez",5)); // Dolor de barriga
c.add(new Enfermo("Andrea Sánchez",3)); // Dolor de cabeza medio
c.add(new Enfermo("Óscar Muñoz",1)); // Dolor de cabeza leve
c.add(new Enfermo("Juliana Ortiz",7)); // Paro respiratorio
c.add(new Enfermo("Pedro Ríos",6)); // Dolor de cabeza fuerte
c.add(new Enfermo("María Ruiz",2)); // Cólicos
c.add(new Enfermo("Andrés Rodríguez",5)); // Dolor de barriga
c.add(new Enfermo("Eliana Guzmán",8)); // Infarto cardiaco
c.add(new Enfermo("Fredy Olarte",3)); // Dolor de cabeza medio
c.add(new Enfermo("Jorge Ramos",1)); // Dolor de cabeza leve
while (!c.isEmpty()) {
Enfermo e=c.poll();
System.out.println("Prioridad: "+e.getPrioridad()+". Enfermo: "+e.getNombre());
}
}
}
ESTRUCTURAS DE DATOS 8
5. APLICACIONES
Se dice que una expresión como la anterior está en notación infija, pues los operadores se
ubican en medio de los operandos. Además, toda operación se coloca entre paréntesis para
facilitar su procesamiento.
Dada una expresión en notación infija, almacenada en una cadena de texto, el siguiente
algoritmo es capaz de evaluar la expresión, dando el resultado numérico de realizar las
operaciones:
ESTRUCTURAS DE DATOS 9
}
public static double evaluarInfijo(String expresion) {
Stack<String> pila=new Stack<String>();
StringTokenizer tokenizer=new StringTokenizer(expresion,"()+-*/",true);
while (tokenizer.hasMoreTokens()) {
String token=tokenizer.nextToken();
char c=token.charAt(0);
if (c=='(') { // Paréntesis de apertura
// Se descarta
}
else if (c=='+'||c=='-'||c=='*'||c=='/') { // Operador
pila.push(token);
}
else if (c!=')') { // Número
pila.push(token);
}
else { // Paréntesis de cierre
String operando2=pila.pop();
String operador=pila.pop();
String operando1=pila.pop();
double f=Double.parseDouble(operando1),g=Double.parseDouble(operando2);
if (operador.equals("+")) {
pila.push(String.valueOf(f+g));
}
else if (operador.equals("-")) {
pila.push(String.valueOf(f-g));
}
else if (operador.equals("*")) {
pila.push(String.valueOf(f*g));
}
else if (operador.equals("/")) {
pila.push(String.valueOf(f/g));
}
}
}
return Double.parseDouble(pila.peek());
}
}
Una expresión en notación posfija (también llamada notación polaca inversa) ubica los
operadores al final:
Investigue sobre la notación posfija, sobre cómo se evalúan estas expresiones, y analice el
programa ExpresionesPosfijas.java, que se incluye dentro del proyecto en Eclipse bajo el
recurso Expresiones.zip.
ESTRUCTURAS DE DATOS 10
EN RESUMEN
Una lista ordenada es una lista sin elementos repetidos que se almacenan de menor a mayor según
cierto criterio de ordenamiento.
Una pila es una estructura de datos lineal que sólo permite inserciones y eliminaciones en uno de los
extremos, llamado tope. En la librería estándar de Java, la clase Stack<E> representa pilas.
Una cola es una estructura de datos lineal que permite inserciones en un extremo (llamado cola) y
eliminaciones del otro extremo (llamado cabeza). En la librería estándar de Java, la interfaz Queue<E>
representa colas.
Una cola de prioridad es una cola donde los elementos son eliminados según el orden establecido por
cierto criterio de prioridad. En la librería estándar de Java, la clase PriorityQueue<E> representa
colas de prioridad.
ESTRUCTURAS DE DATOS 11
ESTRUCTURAS DE DATOS
UNIDAD DOS - SEMANA CUATRO
EJERCICIOS PROPUESTOS *
1.1. Declare la clase VEDLinkedListD<E>, que implementa listas con nodos doblemente
encadenados con apuntador al primero y al último:
public class VEDLinkedListD<E> implements VEDList<E> { // Declaración de la clase
// **********************************
// * Clase que representa los nodos *
// **********************************
private static class VEDNodoD<E> {
E val=null; // Valor del nodo
VEDNodoD<E> ant=null; // Nodo anterior
VEDNodoD<E> sig=null; // Nodo siguiente
VEDNodoD(E pVal) { // Constructor de la clase nodo
val=pVal;
}
}
// *************************
// * Atributos de la clase *
// *************************
// Nodo que guarda el primer elemento de la lista:
private final VEDNodoD<E> primero=null;
// Nodo que guarda el último elemento de la lista:
private final VEDNodoD<E> ultimo=null;
*
Resumen del libro Estructuras de Datos en Java de Alejandro Sotelo Arévalo, cuya publicación está pendiente.
ESTRUCTURAS DE DATOS 1
// Tamaño de la lista (inicia en 0).
private int tamanho=0;
// ***********************
// * Métodos de la clase *
// ***********************
// Constructor de la lista vacía:
public VEDLinkedListD() {
}
}
1.2. Implemente el método clear(), que elimina todos los nodos de la lista.
1.3. Implemente el método privado getNodo(int index), que retorna el nodo que almacena
el elemento que se encuentra en la posición index de la lista, con las siguientes
consideraciones:
Siga el esquema:
private VEDNodoD<E> getNodo(int index) {
if (index<tamanho/2) {
VEDNodoD<E> x=primero;
for (...) { // Falta llenar el for (...)
x=x.sig;
}
return x;
}
else {
VEDNodoD<E> x=ultimo;
for (...) { // Falta llenar el for (...)
x=x.ant;
}
return x;
}
}
Cree un nodo “nuevo” cuyo valor sea “element”, cuyo anterior sea null y cuyo siguiente sea
null (en código: VEDNodoD<E> nuevo=new VEDNodoD<E>(element);).
Si la lista tiene tamaño 0 (o sea, si la inserción se efectúa sobre una lista vacía):
o Asigne al atributo “primero” el nodo “nuevo” (en código: primero=nuevo;).
ESTRUCTURAS DE DATOS 2
o Asigne al atributo “ultimo” el nodo “nuevo” (en código: ultimo=nuevo;).
o Incremente el tamaño de la lista en (en código: tamanho++;).
De lo contrario, si index es 0 (o sea, si la inserción se efectúa al principio de una lista no
vacía):
o Ponga el siguiente del nodo “nuevo” apuntando al nodo “primero” (en código: …).
o Ponga el anterior del nodo “primero” apuntando al nodo “nuevo” (en código: …).
o Asigne al atributo “primero” el nodo “nuevo” (en código: primero=nuevo;).
o Incremente el tamaño de la lista en (en código: tamanho++;).
De lo contrario, si index es tamanho (o sea, si la inserción se efectúa al final de una lista no
vacía):
o Ponga el anterior del nodo “nuevo” apuntando al nodo “ultimo” (en código: …).
o Ponga el siguiente del nodo “ultimo” apuntando al nodo “nuevo” (en código: …).
o Asigne al atributo “ultimo” el nodo “nuevo” (en código: ultimo=nuevo;).
o Incremente el tamaño de la lista en (en código: tamanho++;).
De lo contrario (si la inserción se efectúa en medio de dos nodos):
o Con la ayuda de la función getNodo, obtenemos un apuntador “b” al nodo que almacena el
elemento de la posición index, y declaramos “a” como un apuntador al nodo anterior del
nodo “b”.
o Ponga el anterior del nodo “nuevo” en el nodo “a” y el siguiente del nodo “nuevo” en el
nodo “b”.
o Ponga el siguiente del nodo “a” en el nodo “nuevo” y el anterior del nodo “b” en el nodo
“nuevo”.
o Incremente el tamaño de la lista en (en código: tamanho++;).
ESTRUCTURAS DE DATOS 3
De lo contrario, si index es tamanho (o sea, si la eliminación remueve el último elemento de
la lista):
o Ponga el atributo “ultimo” en el anterior del último nodo (en código:
ultimo=ultimo.ant;).
o Ponga el siguiente del “ultimo” en null (en código: ultimo.sig=null;).
o Decrezca el tamaño de la lista en (en código: tamanho--;).
De lo contrario (si la eliminación se efectúa en medio de dos nodos):
o Con la ayuda de la función getNodo, obtenemos un apuntador “b” al nodo que almacena el
elemento de la posición index, declaramos “a” como un apuntador al nodo anterior del
nodo “b”, y declaramos “c” como un apuntador al nodo siguiente del nodo “b”.
o Ponga el siguiente del nodo “a” en el nodo “c” y el anterior del nodo “c” en el nodo “a”.
o Decrezca el tamaño de la lista en (en código: tamanho--;).
1.8. ¿Por qué su implementación del método add funciona? Explique claramente caso por
caso, usando dibujos.
1.9. ¿Por qué su implementación del método remove funciona? Explique claramente caso por
caso, usando dibujos.
1.11. ¿Por qué será más sencillo implementar una lista doblemente encadenada en anillo con
encabezado?
Considere los siguientes cuatro métodos que eliminan los números pares de una lista:
public static void eliminarValoresParesVersion1(List<Integer> p) {
for (int i=0; i<p.size(); i++) {
if (p.get(i).intValue()%2==0) p.remove(i--);
}
}
public static void eliminarValoresParesVersion2(List<Integer> p) {
List<Integer> q=new LinkedList<Integer>(); // Posiciones a eliminar
for (int i=0; i<p.size(); i++) {
if (p.get(i).intValue()%2==0) q.add(0,i);
}
for (int j=0; j<q.size(); j++) {
p.remove(q.get(j).intValue());
}
}
public static void eliminarValoresParesVersion3(List<Integer> p) {
for (Iterator<Integer> it=p.iterator(); it.hasNext(); ) {
if (it.next().intValue()%2==0) it.remove();
}
}
ESTRUCTURAS DE DATOS 4
public static void eliminarValoresParesVersion4(List<Integer> p) {
for (int x:p) {
if (x%2==0) p.remove(x);
}
}
2.2. Explique detalladamente por qué la versión 2 no funciona si se cambia “ q.add(0,i)” por
“q.add(i)”.
2.3. Justifique claramente por qué las versiones 1, 2 y 3 funcionan (sea cuidadoso con la
versión 3).
2.7. ¿Qué implementación es mejor? ¿Por qué? ¿Qué criterios tiene en cuenta?
3. ITERADORES
4. PALÍNDROMOS
ESTRUCTURAS DE DATOS 5
Cree una lista vacía q instanciando una implementación adecuada que ayude a lograr la
complejidad deseada (debe escoger entre ArrayList<E> y LinkedList<E>).
Cree un iterador it sobre la lista p.
Construya un ciclo que usando el iterador it visite todos los elementos de la lista p. Por
cada elemento iterado, añádalo al principio de la lista q.
Cree un iterador itP sobre la lista p y un iterador itQ sobre la lista q.
Itere en paralelo ambas listas: mientras los iteradores itP e itQ tengan un siguiente
elemento por visitar:
o Guarde en una variable elemP de tipo E el siguiente elemento entregado por el iterador
itP.
o Guarde en una variable elemQ de tipo E el siguiente elemento entregado por el iterador
itQ.
o Si elemP no es igual a elemQ, retorne false.
Retorne true.
4.3. Explique por qué el método tiene complejidad temporal tanto para vectores como
para listas encadenadas.
ESTRUCTURAS DE DATOS 6