Está en la página 1de 73

Estructuras de Datos

Tema 3: Listas
Listas 2

INDICE
1. Introducción.
2. El TAD Lista ordinal.
2.1. Implementación con una lista enlazada.
2.2. Iteradores.
2.3. Uso de listas ordinales.
2.4. Implementación con un array dinámico.
2.5. Comparativa de ambas implementaciones.
2.6. Listas ordinales en la Biblioteca estándar de Java.
3. El TAD Lista Calificada o Diccionario.
3.1. Implementación con una lista enlazada.
3.2. Listas calificadas en la Biblioteca estándar de Java.
4. Otras implementaciones.
4.1. Listas doblemente enlazadas.
4.2. Listas enlazadas con cabecera y centinela.
4.3. Listas enlazadas reorganizables.
Listas 3

1. Introducción.
Definición de lista:
 Un TAD Lista representa una secuencia homogénea y lineal de
elementos.
 Homogénea porque todos los elementos de una lista son del
mismo tipo. Así por ejemplo, se puede tener una lista de enteros,
de cadenas o de objetos Alumno.
 Lineal porque existe una relación lineal entre los elementos: cada
elemento tiene un único sucesor y un único predecesor, excepto el
primero y el último.
Listas 4

1. Introducción.
Clasificación de las listas:
 Listas ordinales: cada elemento se almacena en la lista en base a
su orden de llegada, no depende del valor del elemento. Las pilas
y las colas son ejemplos de listas ordinales. En este tema se verá un
TAD de lista ordinal más general que las pilas y las colas.
 Listas Calificadas o diccionarios: cada elemento de la lista lleva
asociado un campo clave. Por cuestiones de eficiencia, los
elementos se ordenan en base a este campo clave. No puede haber
dos elementos con el mismo campo clave. La idea es que se utilice
el campo clave para buscar la información de un elemento.
Listas 5

1. Introducción.
Implementacion de los TADs listas:
 Para implementar los TADs que se propondrán en este tema, se
utilizarán estructuras de datos de diferentes tipos, realizando
además una comparativa de eficiencia:
• Listas enlazadas. Que tienen la ventaja de ser una estructura
dinámica y que, por tanto, hacen un mejor uso de la memoria.
• Arrays. Los arrays estáticos quedan descartados porque no son
adecuados cuando no se sabe el número de elementos que se van a
almacenar en la lista. En cambio, sí se utilizarán arrays dinámicos, que
cambian de tamaño según sea necesario.
Listas 6

2. El TAD Lista Ordinal.


 Las pilas son estructuras de datos adecuadas en problemas en los
que se necesita acceder al último elemento (LIFO). Las colas son
estructuras de datos adecuadas en problemas en los que se
necesita acceder al primer elemento (FIFO). En estos casos, el uso
de Pilas y Colas proporciona soluciones muy sencillas y eficientes.
 Sin embargo, cuando en un problema se necesita acceder a un
elemento de la lista que no es el primero o el último, el uso de una
Pila o una Cola lleva a soluciones poco eficientes.
 Para estas situaciones se proponen en este tema las listas
ordinales como solución más adecuada. Se trata de una lista más
general, en la que podrá recuperarse un elemento en cualquier
posición de forma más eficiente.
 El uso de esta abstracción en programación es muy habitual.
Mucho más que las Pilas o las Colas.
Listas 7

2. El TAD Lista Ordinal.


Abstracción:
 En el TAD de Lista Ordinal los elementos se almacenan en la lista
según van llegando. La posición no depende del valor del
elemento.
 Por tanto, puede haber elementos repetidos.
 El número de elementos que puede almacenar la lista es
indefinido.
 El tipo de los elementos puede ser cualquiera. Aunque todos del
mismo tipo.
 En las listas ordinales se podrá acceder a cualquiera de los
elementos de la lista.
Todas las características menos esta última son comunes a Pilas y
Colas.
Listas 8

2. El TAD Lista Ordinal.


Abstracción:
A continuación, se propone una interfaz para dar soporte a la
abstracción Lista ordinal de enteros:
 Constructor que inicializa una lista vacía sin elementos.
 void insertar(int dato) --> inserta el dato como
último de la lista, es decir, detrás de todos los que ya estuvieran
en la lista.
 boolean vacia() --> determina si la lista está vacía o no.
 int getElemento(int posición) --> Devuelve el
elemento almacenado en la posición recibida como parámetro.
Listas 9

2. El TAD Lista Ordinal.


Abstracción:
 boolean borrar(int dato) --> Elimina el primer
elemento de la lista que coincida con el parámetro recibido.
Devuelve true si ha conseguido realizar el borrado.
 int posicion(int dato) --> Devuelve la primera
posición en la que se encuentra el dato en la lista. En caso de no
existir dicho dato, devuelve -1.
 boolean contiene(int dato) --> Determina si el dato
recibido como parámetro existe en la lista o no.
 int getNumElementos() --> Devuelve el número de
elementos de la lista.
 void mostrar() --> Visualiza el contenido de la lista.
Listas > 2. El TAD Lista Ordinal. 10

2.1. Implementación con una lista enlazada.


 Como primera solución para la abstracción Lista Ordinal, se
propone una implementación con una lista enlazada, en la que
cada nodo contiene un elemento.
 La lista enlazada utiliza la memoria de forma dinámica, lo cual es
adecuado para la abstracción Lista Ordinal, en la que se permite
un número indefinido de elementos.
 Para manejar la lista enlazada se van a utiliza dos referencias:
• inicio: que apunta al primer nodo de la lista enlazada. A partir de
ella, se puede recorrer la lista para llegar a cualquier elemento.
• fin: que apunta al último nodo de la lista. Por defecto, los elementos
se añaden al final de la lista (operación insertar(int dato)), por lo que
esta referencia será muy útil para hacer inserciones de forma
eficiente.
Listas > 2. El TAD Lista Ordinal. 11

2.1. Implementación con una lista enlazada.

numElementos = 4
Objeto
Lista Ordinal fin
inicio

siguiente
dato

null
10 12 13 21
Objeto Objeto Objeto Objeto
Nodo Nodo Nodo Nodo

 Además de las referencias inicio y fin, el estado de la lista


ordinal contiene el atributo numElementos, para contabilizar el
número de elementos que tiene la lista en cada momento.
Listas > 2. El TAD Lista Ordinal. 12

2.1. Implementación con una lista enlazada.


Se define la clase Nodo, con el contenido de un nodo (dato y
siguiente). Cada vez que se requiera un nuevo elemento en la lista
enlazada, se realizará una operación new Nodo(…).
public class Nodo {
private int dato;
private Nodo siguiente;
public Nodo(int dato, Nodo siguiente) {
this.dato = dato;
this.siguiente = siguiente;
}
public int getDato() {
return dato;
}
public void setDato(int dato) {
this.dato = dato;
}
public Nodo getSiguiente() {
return siguiente;
}
public void setSiguiente(Nodo siguiente) {
this.siguiente = siguiente;
}
}
Listas > 2. El TAD Lista Ordinal. 13

2.1. Implementación con una lista enlazada.


public class ListaOrdinal {
private Nodo inicio, fin;
private int numElementos;
public ListaOrdinal() {
inicio = null;
fin = null;
numElementos = 0;
}
public boolean vacia() {
return inicio == null;
}
public void insertar(int dato) {
Nodo nuevo = new Nodo(dato, null); // Crear un nodo nuevo
// Insertar el nodo al final de la lista enlazada
if (this.vacia()) {
inicio = nuevo;
} else {
fin.setSiguiente(nuevo);
}
fin = nuevo;
numElementos++;
}
Listas > 2. El TAD Lista Ordinal. 14

2.1. Implementación con una lista enlazada.


public void mostrar() {
if (this.vacia()) {
System.out.println("Lista vacía");
} else {
Nodo actual = inicio; // Referencia auxiliar
while (actual != null) {
System.out.println(actual.getDato());
actual = actual.getSiguiente();
}
}
}
Listas > 2. El TAD Lista Ordinal. 15

2.1. Implementación con una lista enlazada.


public int getElemento(int posicion) {
// Avanzar en la lista tantos elementos como 'posicion'
Nodo actual = inicio; // Referencia auxiliar
for (int i = 0; i < posicion; i++) {
actual = actual.getSiguiente();
}
return actual.getDato();
}

public int getNumElementos() {


return numElementos;
}
Listas > 2. El TAD Lista Ordinal. 16

2.1. Implementación con una lista enlazada.


public boolean borrar(int dato) {
Nodo actual = inicio; // Referencia para recorrer la lista
Nodo anterior = null; // Referencia al nodo anterior a actual
while (actual != null && actual.getDato() != dato) { // Buscar dato
anterior = actual;
actual = actual.getSiguiente();
}
if (actual != null) { // Dato encontrado. Borrar nodo actual
if (actual == inicio) { // Borrar el primero de la lista
inicio = actual.getSiguiente();
} else { // Borrar nodo que no es el primero
anterior.setSiguiente(actual.getSiguiente());
}
if (actual == fin) { // Se ha borrado el ultimo de la lista
fin = anterior;
}
numElementos--;
return true;
} else {
return false;
}
}
Listas > 2. El TAD Lista Ordinal. 17

2.1. Implementación con una lista enlazada.


public int posicion(int dato) {
Nodo actual = inicio; // Referencia auxiliar
int posicion = 0;
while (actual != null && actual.getDato() != dato) {
actual = actual.getSiguiente();
posicion++;
}
if (actual != null) { // Dato encontrado
return posicion;
} else {
return -1;
}
}

public boolean contiene(int dato) {


return this.posicion(dato) >= 0;
}
} // Fin de la clase ListaOrdinal
Listas > 2. El TAD Lista Ordinal. 18

2.2. Iteradores.
Es posible recorrer una lista ordinal utilizando el método
getElemento de la interfaz, como se muestra en el siguiente ejemplo:
public class Aplicacion {
public static void main(String[] args) {
ListaOrdinal lista = new ListaOrdinal();
for (int i = 0; i < 5; i++) {
lista.insertar(i);
lista.insertar(i * i);
}
System.out.print(lista.getNumElementos() + " elementos: ");
int numElem = lista.getNumElementos();
for (int i = 0; i < numElem; i++) {
System.out.print(lista.getElemento(i) + " ");
}
}
}

10 elementos: 0 0 1 1 2 4 3 9 4 16
Listas > 2. El TAD Lista Ordinal. 19

2.2. Iteradores.
 En el ejemplo anterior, cada vez que se llama al método
getElemento(i) comienza desde el nodo inicial de la lista hasta
llegar a la posición i. Así, para recorrer una lista de n elementos
habría que pasar por un total de ∑𝑛𝑛𝑖𝑖=1 𝑖𝑖 nodos de la lista. Por
ejemplo, si la lista tiene 1.000 elementos, habría que pasar por
500.500 nodos.
 En consecuencia, podemos afirmar que el recorrido de la lista es
bastante poco eficiente.
 El uso de iteradores para hacer recorridos en una lista consigue
soluciones más eficientes.
 Además, los iteradores constituyen un patrón de diseño en la
programación, es decir, una solución cuya efectividad ha quedado
probada y que se recomienda utilizar.
Listas > 2. El TAD Lista Ordinal. 20

2.2. Iteradores.
 Un objeto iterador “apunta” a un elemento de la lista.
Inicialmente “apunta” al primer elemento, pudiendo ofrecer
dicho elemento y avanzar al siguiente. Este razonamiento se
puede seguir aplicando hasta llegar al final de la lista.
 Las operaciones básicas que ofrece un iterador en Java son
dos:
• boolean hasNext() --> devuelve true cuando el elemento
al que “apunta” existe. Por el contrario, devuelve false si la lista
se ha acabado, es decir, cuando el elemento “apuntado” no
existe.
• int next() --> devuelve el elemento “apuntado” por el
iterador, que en nuestra lista ordinal es un entero. Además,
avanza el iterador al siguiente elemento de la lista.
Listas > 2. El TAD Lista Ordinal. 21

2.2. Iteradores.
 La forma de recorrer la lista utilizando un iterador es la siguiente:
public class Aplicacion {
public static void main(String[] args) {
ListaOrdinal lista = new ListaOrdinal();
for (int i = 0; i < 5; i++) {
lista.insertar(i);
lista.insertar(i * i);
}
System.out.print(lista.getNumElementos() + " elementos: ");

Iterador it = lista.getIterador();
while (it.hasNext()) {
System.out.print(it.next());
}
}
}

10 elementos: 0 0 1 1 2 4 3 9 4 16
Listas > 2. El TAD Lista Ordinal. 22

2.2. Iteradores.
 De esta manera, se realiza el recorrido a la lista de forma más
eficiente, pasando una única vez por cada nodo. Así, para una lista
de 1.000 elementos, se pasaría únicamente por 1.000 nodos.
 Implementación de los iteradores:
1. Añadir el método getIterador a la interfaz de la clase
ListaOrdinal. Este método crea y devuelve un objeto Iterador,
que apunta al primer elemento de la lista ordinal:

public class ListaOrdinal {


...
public Iterador getIterador() {
return new Iterador(inicio);
}
}
Listas > 2. El TAD Lista Ordinal. 23

2.2. Iteradores.
2. Implementación de la clase Iterador:

public class Iterador {


private Nodo actual; // Referencia al elemento actual de la lista
public Iterador(Nodo actual) {
this.actual = actual;
}
public boolean hasNext() {
return actual != null;
}
public int next() {
int resultado = actual.getDato();
actual = actual.getSiguiente();
return resultado;
}
}
Listas > 2. El TAD Lista Ordinal. 24

2.3. Uso de listas ordinales.


El uso de esta abstracción en programación es muy habitual. Se utiliza
siempre que se necesite manejar un número indeterminado de elementos
del mismo tipo y acceder a ellos sin restricción.
Ejemplo: Definición de un TAD Alumno.
Además de los datos personales del alumno, incluye su expediente, formado
por todas las evaluaciones que haya hecho en todas las asignaturas. Teniendo
en cuenta que un alumno se puede haber evaluado en una misma asignatura
varias veces y que el número de evaluaciones de un alumno es indefinido, se
decide utilizar una lista ordinal de evaluaciones para almacenar el expediente.
Se define el TAD Evaluación con el nombre de la asignatura, la convocatoria y
la nota. La interfaz es la siguiente:
• Constructor con todos los datos de la evaluación.
• getNombreAsignatura
• getConvocatoria
• getNota.
Listas > 2. El TAD Lista Ordinal. 25

2.3. Uso de listas ordinales


 El TAD Evaluación se define a través de la siguiente clase:
public class Evaluacion {
private String nombreAsignatura;
private String convocatoria;
private double nota;
public Evaluacion(String nombreAsignatura, String convocatoria,
double nota) {
this.nombreAsignatura = nombreAsignatura;
this.convocatoria = convocatoria;
this.nota = nota;
}
public String getNombreAsignatura(){
return nombreAsignatura;
}
public String getConvocatoria() {
return convocatoria;
}
public double getNota() {
return nota;
}
}
Listas > 2. El TAD Lista Ordinal. 26

2.3. Uso de listas ordinales.


Se define el TAD ListaOrdinal, pero ahora de objetos Evaluacion en lugar
de datos int.
Finalmente, definimos el TAD Alumno a través de la siguiente interfaz:
• Constructor con el nombre y la matrícula del alumno.
• String getNombre().
• int getMatricula().
• void insertarEvaluacion(Evaluacion evaluación): añade
una nueva evaluación al alumno.
• boolean estaAprobado(String nombreAsig): determina si el
alumno ha aprobado en la asignatura nombreAsig.
Listas > 2. El TAD Lista Ordinal. 27

2.3. Uso de listas ordinales


public class Alumno {
private String nombre;
private int matricula;
private ListaOrdinal expediente;
public Alumno(String nombre, int matricula) {
this.nombre = nombre;
this.matricula = matricula;
expediente = new ListaOrdinal();
}
public String getNombre() {
return nombre;
}
public int getMatricula() {
return matricula;
}
Listas > 2. El TAD Lista Ordinal. 28

2.3. Uso de listas ordinales


public void insertarEvaluacion(Evaluacion evaluacion) {
expediente.insertar(evaluacion);
}
public boolean estaAprobado(String nombreAsig) {
boolean aprobado = false;
Iterador it = expediente.getIterador();
while (it.hasNext() && !aprobado) {
Evaluacion evaluacion = it.next();
if (evaluacion.getNombreAsignatura().equals(nombreAsig)
&& evaluacion.getNota() >= 5.0) {
aprobado = true;
}
}
return aprobado;
}
} // Fin clase Alumno
Listas > 2. El TAD Lista Ordinal. 29

2.3. Uso de listas ordinales


public class Aplicacion {
public static void main(String[] args) {
Alumno alberto = new Alumno("Alberto García Gómez", 1253);
Evaluacion ed1 = new Evaluacion("ED", "Junio 19", 2.5);
Evaluacion ed2 = new Evaluacion("ED", "Junio 20", 3.0);
Evaluacion ed3 = new Evaluacion("ED", "Julio 20", 7.4);
alberto.insertarEvaluacion(ed1);
alberto.insertarEvaluacion(ed2);
alberto.insertarEvaluacion(ed2);
if (alberto.estaAprobado("ED")) {
System.out.println(alberto.getNombre() + " ha aprobado en ED");
} else {
System.out.println(alberto.getNombre() + " no ha aprobado en ED");
}
if (alberto.estaAprobado("Algebra")) {
System.out.println(alberto.getNombre() + " ha aprobado en Algebra");
} else {
System.out.println(alberto.getNombre() + " no ha aprobado en Algebra");
}
}
}

Alberto García Gómez ha aprobado en ED


Alberto García Gómez no ha aprobado en Algebra
Listas > 2. El TAD Lista Ordinal. 30

2.4. Implementación con un array dinámico.


 Se implementa a continuación el mismo TAD ListaOrdinal de enteros,
pero ahora la estructura de datos interna será un array, en lugar de una
lista enlazada.
 Si se utilizara un array de tamaño estático, la solución presentaría las
deficiencias propias del tamaño estático. Además, la lista ordinal es
una abstracción de naturaleza dinámica, ya que el número de
elementos es indeterminado.
 En definitiva, se va a utilizar un array dinámico como estructura de
datos. Este array aumentará de tamaño cuando, estando completo, se
necesite almacenar un elemento más. Más concretamente, en esta
implementación se duplicará el tamaño.
 El estado de la clase ListaOrdinal se define con los siguientes atributos:
• elementos: es el array que almacena los elementos de la lista.
• capacidad: es el tamaño del array, que puede cambiar.
• numElementos: el número de elementos que contiene la lista.
Listas > 2. El TAD Lista Ordinal. 31

2.4. Implementación con un array dinámico.


public class ListaOrdinal {
private static final int CAPACIDAD_INICIAL = 5;
private int[] elementos;
private int numElementos;
private int capacidad;

public ListaOrdinal() { // Constructor


elementos = new int[CAPACIDAD_INICIAL];
capacidad = CAPACIDAD_INICIAL;
numElementos = 0;
}
public boolean vacia() {
return numElementos == 0;
}

Se define un constructor que crea un array inicial para únicamente


5 datos. La constante CAPACIDAD_INICIAL define ese valor 5.
Listas > 2. El TAD Lista Ordinal. 32

2.4. Implementación con un array dinámico.

public void insertar(int dato) {


if (numElementos == capacidad) {
this.doblarCapacidad();
}
elementos[numElementos] = dato;
numElementos++;
}

// Duplica el tamaño del array


private void doblarCapacidad() {
capacidad = capacidad * 2;
int[] aux = new int[capacidad];
for (int i = 0; i < numElementos; i++) {
aux[i] = elementos[i];
}
elementos = aux;
}
Listas > 2. El TAD Lista Ordinal. 33

2.4. Implementación con un array dinámico.


Método doblarCapacidad:
vector
4 12 3 1 77

aux
4 12 3 1 77

Finalmente, vector = aux, con lo que la referencia vector apunta al


array de 10 elementos. Además, el array de 5 elementos será liberado
de la memoria por no existir ninguna referencia al mismo.
El método doblarCapacidad es privado porque no pertenece al
interfaz de la clase. Se necesita para implementar el método insertar.
Listas > 2. El TAD Lista Ordinal. 34

2.4. Implementación con un array dinámico.

public void mostrar() {


if (this.vacia()) {
System.out.println ("Lista vacia");
} else {
for (int i = 0; i < numElementos; i++) {
System.out.println(elementos[i]);
}
}
}

public int getElemento(int posicion) {


return elementos[posicion];
}
Listas > 2. El TAD Lista Ordinal. 35

2.4. Implementación con un array dinámico.


public boolean borrar(int dato) {
int actual = 0;
// Buscar dato
while (actual < numElementos && elementos[actual] != dato) {
actual++;
}
if (actual < numElementos) { // Dato encontrado. Borrar actual
for (int i = actual; i < numElementos - 1; i++) {
elementos[i] = elementos[i + 1]; // Rellenar hueco
}
numElementos--;
return true;
} else {
return false;
}
}

public int getNumElementos() {


return numElementos;
}
Listas > 2. El TAD Lista Ordinal. 36

2.4. Implementación con un array dinámico.

public int posicion(int dato) {


int actual = 0;
// Buscar dato
while (actual < numElementos && elementos[actual] != dato) {
actual++;
}
if (actual < numElementos) { // Dato encontrado
return actual;
} else {
return -1;
}
}

public boolean contiene(int dato) {


return this.posicion(dato) >= 0;
}

public Iterador getIterador() {


return new Iterador(elementos, numElementos, 0);
}
} // Fin de la clase ListaOrdinal
Listas > 2. El TAD Lista Ordinal. 37

2.4. Implementación con un array dinámico.

public class Iterador {


private int[] elementos;
private int numElementos;
private int actual;
public Iterador(int[] elementos, int numElementos, int actual ) {
this.elementos = elementos;
this.numElementos = numElementos;
this.actual = actual;
}
public boolean hasNext() {
return actual < numElementos;
}
public int next() {
int resultado = elementos[actual];
actual++;
return resultado;
}
}
Listas > 2. El TAD Lista Ordinal. 38

2.5. Comparativa de ambas implementaciones.


A continuación, se realiza una comparativa entre las dos
implementaciones realizadas para el TAD Lista Ordinal: una lista
enlazada y un array dinámico.
 El uso de memoria es más eficiente con lista enlazada. Por ejemplo, en
un array dinámico puede ocurrir que se tenga un array de 1.000
posiciones para una lista de 501 elementos.
 El método insertar es igual de eficiente en ambas implementaciones,
ya que en ambas se inserta directamente. Sin embargo, el array
dinámico penaliza cada vez que se tiene que doblar el tamaño del
mismo, ya que hay que hay que copiar todo el contenido.
 El método borrar es más eficiente en la lista enlazada, ya que en el
array dinámico hay que desplazar el resto de elementos para evitar
que queden huecos.
Listas > 2. El TAD Lista Ordinal. 39

2.5. Comparativa de ambas implementaciones.


 El método getElemento es más eficiente en el array dinámico, ya que el
acceso es directo, mientras que en la lista enlazada hay que recorrer
los nodos hasta llegar a la posición. El uso de iteradores compensa
esta deficiencia en la implementación con lista enlazada si se trata de
recorridos. No así si se trata de accesos aleatorios.
 Los métodos posición y contiene son similares en ambas
implementaciones.

Como conclusión final, en una aplicación es más eficiente utilizar


una ListaOrdinal implementada con una lista enlazada, salvo que en
dicha aplicación haya que realizar muchos accesos aleatorios.
Listas > 2. El TAD Lista Ordinal. 40

2.6. Listas ordinales en la biblioteca estándar de Java.


 Para las listas ordinales se pueden considerar dos clases en el
paquete java.util: LinkedList y ArrayList

La clase LinkedList<E>:
 Implementa la abstracción lista ordinal a través de una lista
enlazada. Con la peculiaridad de que esta lista es doblemente
enlazada.
 Tiene un parámetro <E>, por lo que se trata de una clase
parametrizada de Java. A través de este parámetro, se puede
determinar el tipo componente de la lista ordinal:
• Si se quiere una lista de enteros:
LinkedList<Integer> lista = new LinkedList<Integer>()
• Si se quiere una lista de objetos de la clase Alumno:
LinkedList<Alumno> lista = new LinkedList<Alumno>()
Listas > 2. El TAD Lista Ordinal. 41

2.6. Listas ordinales en la biblioteca estándar de Java.


Puede verse el API de la clase LinkedList<E> en este enlace. A
continuación, se muestran algunos métodos, que son los que se
corresponden nuestra interfaz de ListaOrdinal:
• Constructor que crea una lista ordinal vacía.
• void add(E e) --> Añade el elemento e al final de la lista.
• boolean isEmpty() --> determina si la lista está vacía o no.
• E get(int i) --> devuelve el elemento que ocupa la posición i.
• boolean remove(E e) --> borra el elemento e de la lista, si existe.
• int indexOf(E e) --> devuelve la primera posición del elemento
e.
• boolean contains(E e) --> determina si el elemento e está en la
lista.
• int size() --> determina el número de elementos de la lista.
• Iterator<E> iterator() --> devuelve un iterador para la lista.
Listas > 2. El TAD Lista Ordinal. 42

2.6. Listas ordinales en la biblioteca estándar de Java.


import java.util.Iterator;
import java.util.LinkedList;
public class Aplicacion {
public static void main(String[] args) {
LinkedList<Integer> lista = new LinkedList<Integer>();
for (int i = 0; i < 5; i++) {
lista.add(i);
lista.add(i * i);
}
System.out.print(lista.size() + " elementos: ");
Iterator<Integer> it = lista.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
}
}

10 elementos: 0 0 1 1 2 4 3 9 4 16
Listas > 2. El TAD Lista Ordinal. 43

2.6. Listas ordinales en la biblioteca estándar de Java.


La clase ArrayList<E>:
 Implementa la abstracción Lista ordinal a través de un array
dinámico. Cuando el array se completa, aumenta el tamaño un
50%.
 Al igual que LinkedList, se trata una clase parametrizada para
poder determinar el tipo de componente de la lista.
 El API de esta clase se puede consultar en este enlace.
 Como implementa la misma abstracción que LinkedList, los
métodos anteriores también se encuentran en ArrayList.
Listas > 2. El TAD Lista Ordinal. 44

2.6. Listas ordinales en la biblioteca estándar de Java.


import java.util.Iterator;
import java.util.ArrayList;
public class Aplicacion {
public static void main(String[] args) {
ArrayList<Integer> lista = new ArrayList<Integer>();
for (int i = 0; i < 5; i++) {
lista.add(i);
lista.add(i * i);
}
System.out.print(lista.size() + " elementos: ");
Iterator<Integer> it = lista.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
}
}

10 elementos: 0 0 1 1 2 4 3 9 4 16
Listas 45

3. El TAD Lista Calificada o Diccionario.


Abstracción:
 En este TAD, cada elemento contiene un campo clave, además de la
información propia de cada elemento. Por ejemplo, una lista de alumnos,
donde la clave es el número de matrícula de cada alumno.
 En una lista calificada no puede haber dos elementos con el mismo
campo clave (no puede haber dos alumnos con el mismo número de
matrícula).
 El tipo de los elementos puede ser cualquiera. Aquí se implementará una
lista de objetos Alumno, con el número de matrícula como clave.
 Los elementos se ordenan en base al campo clave. De manera que los
recorridos en la lista se realizan de forma más eficiente.
 El uso de esta abstracción en programación es también muy habitual. Si
existe un campo clave en los datos, el acceso a los mismos es más
eficiente utilizando dicha clave.
Listas 46

3. El TAD Lista Calificada o Diccionario.


Abstracción:
A continuación, se propone una interfaz para dar soporte a la
abstracción Lista Calificada de objetos Alumno.
 Constructor que inicializa una lista vacía sin elementos.
 boolean insertar(int clave, Alumno dato) -->
inserta el alumno en la posición que le corresponde según la
clave. Devuelve true si consigue realizar la inserción. En caso de
que la clave ya se encontrara en la lista, no inserta el alumno y
devuelve false.
 boolean vacia() --> determina si la lista está vacía.
 Alumno getElemento(int clave) --> Busca la clave en la
lista. Si la encuentra devuelve el alumno asociado a dicha clave,
y si no la encuentra devuelve null.
Listas 47

3. El TAD Lista Calificada o Diccionario.


Abstracción:
 boolean contiene(int clave) --> Determina si en la
lista existe el alumno con el número de matrícula clave.
 boolean borrar(int clave) --> Elimina de la lista el
alumno con número de matrícula clave, en caso de existir.
Devuelve true si ha conseguido realizar el borrado.
 int getNumElementos() --> Devuelve el número de
alumnos que contiene la lista.
 Iterador getIterador() --> Devuelve un iterador para
recorrer la lista.
Listas > 3. El TAD Lista Calificada o Diccionario. 48

3.1. Implementación con una lista enlazada.


 Como primera solución, se propone una implementación con una
estructura de datos del tipo lista enlazada, en la que cada nodo contiene
un objeto Alumno y su clave asociada, que es la matrícula.
 Es decir, la clave (matrícula del alumno) se considera como un campo
adicional, fuera del objeto Alumno. Pero perfectamente podríamos haber
situado la matrícula dentro del objeto Alumno.
 El estado de la lista enlazada lo componen una referencia inicio, que
apunta al primer nodo de la lista enlazada, y el numElementos que indica
el número de elementos de la lista:
Clave (matrícula)

Objeto Alumno

Siguiente
numElementos
Alberto Ana Jorge María

null
inicio 21 García 29 Pérez 71 Díaz 91 Gómez
…. …. …. ….
Objeto Objeto Objeto Objeto Objeto
Lista Calificada Nodo Nodo Nodo Nodo
Listas > 3. El TAD Lista Calificada o Diccionario. 49

3.1. Implementación con una lista enlazada.


Sea la siguiente clase Alumno:
public class Alumno {
private String nombre;
private String direccion;
public Alumno(String nombre, String direccion) {
this.nombre = nombre;
this.direccion = direccion;
}
public String getNombre() {
return nombre;
}
public String getDireccion() {
return direccion;
}
public void mostrar() {
System.out.println ("Nombre: " + nombre
+ " Direccion: " + direccion);
}
}
Listas > 3. El TAD Lista Calificada o Diccionario. 50

3.1. Implementación con una lista enlazada.


Los nodos de la lista enlazada se pueden definir como:

public class Nodo {


private int clave;
private Alumno dato;
private Nodo siguiente;

public Nodo(int clave, Alumno dato, Nodo siguiente) {


this.clave = clave;
this.dato = dato;
this.siguiente = siguiente;
}
public int getClave() { return clave;}
public void setClave(int clave) { this.clave = clave; }
public Alumno getDato() { return dato; }
public void setDato(Alumno dato) { this.dato = dato; }
public Nodo getSiguiente() { return siguiente; }
public void setSiguiente(Nodo siguiente) { this.siguiente = siguiente; }
}
Listas > 3. El TAD Lista Calificada o Diccionario. 51

3.1. Implementación con una lista enlazada.


public class ListaCalificada {
private Nodo inicio;
private int numElementos;
public ListaCalificada() {
inicio = null;
numElementos = 0;
}
public boolean vacia() {
return inicio == null;
}
public int getNumElementos() {
return numElementos;
}
public Iterador getIterador() {
return new Iterador(inicio);
}
Listas > 3. El TAD Lista Calificada o Diccionario. 52

3.1. Implementación con una lista enlazada.


public boolean insertar(int clave, Alumno dato) {
Nodo anterior = null;
Nodo actual = inicio;
// Buscar posición de inserción
while (actual != null && actual.getClave() < clave) {
anterior = actual;
actual = actual.getSiguiente();
}
if (actual == null || actual.getClave() > clave) {
// Insertar antes de actual
Nodo nuevo = new Nodo(clave, dato, actual);
if (actual == inicio) { // Insertar primero de la lista
inicio = nuevo;
} else {
anterior.setSiguiente(nuevo);
}
numElementos++;
return true;
} else {
return false;
}
}
Listas > 3. El TAD Lista Calificada o Diccionario. 53

3.1. Implementación con una lista enlazada.


public Alumno getElemento(int clave) {
Nodo actual = inicio;
// Buscar la clave
while (actual != null && actual.getClave() < clave) {
actual = actual.getSiguiente();
}
if (actual == null || actual.getClave() > clave) {
// No existe la clave
return null;
} else { // Clave encontrada en nodo actual
return actual.getDato();
}
}

public boolean contiene(int clave) {


return this.getElemento(clave) != null;
}
Listas > 3. El TAD Lista Calificada o Diccionario. 54

3.1. Implementación con una lista enlazada.


public boolean borrar(int clave) {
Nodo actual = inicio;
Nodo anterior = null;
// Buscar la clave
while (actual != null && actual.getClave() < clave) {
anterior = actual;
actual = actual.getSiguiente();
}
if (actual == null || actual.getClave() > clave) {
// No existe clave
return false;
} else { // Clave encontrada. Borrar
if (actual == inicio) { // Borrar el primero de la lista
inicio = actual.getSiguiente();
} else {
anterior.setSiguiente(actual.getSiguiente());
}
numElementos--;
return true;
}
}
} // Fin de la clase ListaCalificada
Listas > 3. El TAD Lista Calificada o Diccionario. 55

3.1. Implementación con una lista enlazada.


Y la clase Iterador es prácticamente igual que la del TAD ListaOrdinal:

public class Iterador {


private Nodo actual;

public Iterador(Nodo actual) {


this.actual = actual;
}

public boolean hasNext() {


return actual != null;
}

public Alumno next() {


int resultado = actual.getDato();
actual = actual.getSiguiente();
return resultado;
}
}
Listas > 3. El TAD Lista Calificada o Diccionario. 56

3.1. Implementación con una lista enlazada.


Ejemplo de uso de la clase ListaCalificada:
public class Aplicacion {
public static void main(String[] args) {
ListaCalificada lista = new ListaCalificada();
Alumno pepe = new Alumno("Pepe", "Pso. La Parra");
Alumno juan = new Alumno("Juan", "C/ El Duende");
Alumno sandra = new Alumno("Sandra", "C/ Miralles");
Alumno maria = new Alumno("María", "Avda. Marqués de Lino");
lista.insertar(1, pepe);
lista.insertar(7, juan);
lista.insertar(17, sandra);
lista.insertar(27, maria);
System.out.println("Tiene " + lista.getNumElementos() + " alumnos:");
Iterador it = lista.getIterador();
while (it.hasNext()) {
it.next().mostrar();
}
System.out.println("--------------------------------");
Listas > 3. El TAD Lista Calificada o Diccionario. 57

3.1. Implementación con una lista enlazada.


Ejemplo de uso de la clase ListaCalificada:

// Continúa main
Alumno alumno = lista.getElemento(7);
if (alumno != null) {
System.out.println("El alumno con matrícula 7 es:");
alumno.mostrar();
}
for (int i = 0; i < 10; i++) {
lista.borrar(i);
}
System.out.println("\nDespués de borrar las claves < 10 quedan “
+ lista.getNumElementos() + " alumnos");
it = lista.getIterador();
while (it.hasNext()) {
it.next().mostrar();
}
System.out.println("--------------------------------");
} // Final de main
} // Final de clase Aplicacion
Listas > 3. El TAD Lista Calificada o Diccionario. 58

3.1. Implementación con una lista enlazada.


Ejemplo de uso de la clase ListaCalificada:
Resultado:
Tiene 4 alumnos:
Nombre: Pepe Direccion: Pso. La Parra
Nombre: Juan Direccion: C/ El Duende
Nombre: Sandra Direccion: C/ Miralles
Nombre: María Direccion: Avda. Marqués de Lino
--------------------------------
El alumno con matrícula 7 es:
Nombre: Juan Direccion: C/ El Duende

Después de borrar las claves menores de 10 quedan 2 alumnos


Nombre: Sandra Direccion: C/ Miralles
Nombre: María Direccion: Avda. Marqués de Lino
--------------------------------
Listas > 3. El TAD Lista Calificada o Diccionario. 59

3.2. Listas calificadas en la biblioteca estándar de Java.


 El TAD ListaCalificada o Diccionario es implementado en las clases
TreeMap y HashMap de la biblioteca estándar de Java.
 Sin embargo, ninguna de ellas utilizan una lista enlazada para
implementar el TAD. Concretamente, TreeMap utiliza un árbol binario
de búsqueda y HashMap utiliza una tabla hash, que son estructuras más
eficientes que las listas enlazadas.
 No obstante, vemos a continuación un ejemplo de uso de una clase de
estas, por ejemplo, TreeMap. Como implementa el mismo TAD
ListaCalificada, se utiliza igual.
 En los temas correspondientes a árboles y tablas hash volveremos a ver
algunos detalles más de estas clases.
 La clase TreeMap es una clase parametrizada con dos parámetros <K,V>.
A través de los cuales se determina el tipo de la clave (K) y el tipo de los
datos (V). Por ejemplo, si se quiere una lista calificada de alumnos en
donde la clave sea entera:
TreeMap<Integer, Alumno> lista =
new TreeMap<Integer, Alumno>();
Listas > 3. El TAD Lista Calificada o Diccionario. 60

3.2. Listas calificadas en la biblioteca estándar de Java.


Puede verse el API de TreeMap<K,V> en este enlace. A continuación,
se muestran algunos métodos:
• Constructor que crea un diccionario sin elementos.
• V put(K key, V value) --> Añade el elemento value con la
clave key a la lista calificada. Si el elemento con clave key ya existía
en la lista, lo sustituye con el nuevo value.
• int size() --> Devuelve el número de elementos de la lista
calificada.
• V get(K key) --> Devuelve el elemento asociado a la clave key. En
caso de no existir dicho elemento, devuelve null.
• boolean containsKey(K key) --> determina si existe un
elemento en la lista con clave key.
• V remove(K key) --> borra el elemento que tiene como clave key
en la lista, si existe. Devuelve el elemento borrado, o null si no existía.
Listas > 3. El TAD Lista Calificada o Diccionario. 61

3.2. Listas calificadas en la biblioteca estándar de Java.


import java.util.TreeMap;
public class AplicacionTreeMap {
public static void main(String[] args) {
TreeMap<Integer, Alumno> lista = new TreeMap<Integer, Alumno>();
Alumno pepe = new Alumno("Pepe", "Pso. La Parra");
Alumno juan = new Alumno("Juan", "C/ El Duende");
Alumno sandra = new Alumno("Sandra", "C/ Miralles");
Alumno maria = new Alumno("María", "Avda. Marqués de Lino");
lista.put(7, pepe);
lista.put(17, juan);
lista.put(1, sandra);
lista.put(27, maria);
System.out.println("\nLa lista tiene " + lista.size() + " alumnos.");
Alumno alumno = lista.get(7);
if (alumno != null) {
System.out.println("\nEl alumno con matrícula 7 es:");
alumno.mostrar();
}
lista.remove(7);
if (!lista.containsKey(7)) {
System.out.println("\nAhora no existe el alumno con matrícula 7");
}
}
}
Listas > 3. El TAD Lista Calificada o Diccionario. 62

3.2. Listas calificadas en la biblioteca estándar de Java.


Resultado:

La lista tiene 4 alumnos.


El alumno con matrícula 7 es:
Nombre: Pepe Direccion: Pso. La Parra
Ahora no existe el alumno con matrícula 7
Listas. 63

4. Otras implementaciones.
4.1. Listas doblemente enlazadas.
 Cada nodo está enlazado con el elemento anterior y con el siguiente. De
manera que la lista puede recorrerse en ambos sentidos.
 El elemento anterior al primero es null.
 El elemento siguiente al último es null.

Numero mat. (clave)

Objeto Alumno

Siguiente
Anterior

numElementos
Alberto Ana Jorge María

null
null

21 García 29 Pérez 71 Díaz 91 Gómez


inicio …. …. …. ….

Objeto Objeto Objeto Objeto Objeto


Lista Calificada Nodo Nodo Nodo Nodo
Listas > 4. Otras implementaciones. 64

4.1. Listas doblemente enlazadas.


 Ventaja: son más versátiles, ya que se puede recorrer la lista en
ambos sentidos.
Recordemos que la clase LinkedList de la biblioteca estándar de
Java utiliza listas doblemente enlazadas para implementar las
listas ordinales.
 Inconveniente: dificulta los algoritmos de modificación
(inserción y borrado), ya que se han de actualizar más enlaces.
Sirven para implementar tanto un TAD ListaOrdinal, como para
un TAD ListaCalificada. Lógicamente, cuando se desea poder
hacer recorridos en ambas direcciones.
A continuación se vuelve a definir el TAD ListaCalificada de
alumnos y clave el número de matricula, con listas doblemente
enlazadas.
Listas > 4. Otras Implementaciones. 65

4.1. Listas doblemente enlazadas.


Los nodos de la lista doblemente enlazada quedan como sigue:
public class Nodo {
private int clave;
private Alumno dato;
private Nodo siguiente, anterior;
public Nodo(int clave, Alumno dato, Nodo siguiente, Nodo anterior) {
this.clave = clave;
this.dato = dato;
this.siguiente = siguiente;
this.anterior = anterior;
}
public int getClave() { return clave;}
public void setClave(int clave) { this.clave = clave; }
public Alumno getDato() { return dato; }
public void setDato(Alumno dato) { this.dato = dato;}
public Nodo getSiguiente() { return siguiente; }
public void setSiguiente(Nodo siguiente) { this.siguiente = siguiente; }
public Nodo getAnterior() { return anterior; }
public void setAnterior(Nodo anterior) { this.anterior = anterior; }
}
Listas > 4. Otras implementaciones. 66

4.1. Listas doblemente enlazadas.


Para insertar un nuevo elemento en la lista, hay que reorganizar los
enlaces de la siguiente manera:

Alberto Ana Jorge María


null

null
inicio 21 García 29 Pérez 71 Díaz 91 Gómez
…. …. …. ….

Eva
49 Arias
….

Hay que considerar además la posibilidad de que el nuevo nodo se


inserte el primero de la lista, con lo que no habría nodo anterior. De forma
similar, puede ocurrir que se inserte el último de la lista, con lo que no
habría nodo posterior.
Listas > 4. Otras implementaciones. 67

4.1. Listas doblemente enlazadas.


public boolean insertar(int clave, Alumno dato) {
Nodo anterior = null;
Nodo actual = inicio;
while (actual != null && actual.getClave() < clave) {
anterior = actual;
actual = actual.getSiguiente();
}
if (actual == null || actual.getClave() > clave) {
// Insertar entre anterior y actual
Nodo nuevo = new Nodo(clave, dato, actual, anterior);
if (anterior == null) { // Es el primero de la lista
inicio = nuevo;
} else {
anterior.setSiguiente(nuevo);
}
if (actual != null) { // No es el ultimo de la lista
actual.setAnterior(nuevo);
}
numElementos++;
return true;
} else {
return false;
}
}
Listas > 4. Otras implementaciones. 68

4.1. Listas doblemente enlazadas.


Para borrar un nuevo elemento de la lista, hay que reorganizar los enlaces
de la siguiente manera:

Alberto Ana Jorge María


null

null
inicio 21 García 29 Pérez 71 Díaz 91 Gómez
…. …. …. ….

Nuevamente, hay que considerar la posibilidad de que se borre el


primero de la lista, con lo que no habría nodo anterior. Así como de que
se borre el último de la lista, con lo que no habría nodo posterior.
Listas > 4. Otras implementaciones. 69

4.1. Listas doblemente enlazadas.


public boolean borrar(int clave) {
Nodo actual = inicio;
while (actual != null && actual.getClave() < clave) {
actual = actual.getSiguiente();
}
if (actual == null || actual.getClave() > clave) {
return false;
} else { // Clave encontrada. Borrar actual.
Nodo sigActual = actual.getSiguiente();
Nodo antActual = actual.getAnterior();
if (actual == inicio) { // Es el primero de la lista
inicio = sigActual;
} else {
antActual.setSiguiente(sigActual);
}
if (sigActual != null) { // No es el ultimo de la lista
sigActual.setAnterior(antActual);
}
numElementos--;
return true;
}
}
Listas > 4. Otras implementaciones. 70

4.2. Listas enlazadas con cabecera y centinela.

 La lista siempre contiene dos nodos falsos (sin contenido)


1. Uno al principio, que es la cabecera de la lista.
2. Otro al final, que es el centinela de la lista.
 Para manejar la lista se utilizan sendas referencias a estos nodos:

numElementos
Objeto Lista
Calificada centinela

cabecera

Ana Jorge

null
29 Pérez 71 Díaz
…. ….

Objeto Objeto Objeto Objeto


Nodo Nodo Nodo Nodo
Listas > 4. Otras implementaciones. 71

4.2. Listas enlazadas con cabecera y centinela.


Ventajas:
1. Se consigue simetría en la lista enlazada, ya que todos los nodos no
falsos tienen un antecesor y un sucesor. Por tanto, todos los nodos
se tratan igual (se facilitan los algoritmos).
2. En los algoritmos de búsqueda, inserción y borrado, se colocará en
el centinela la clave del elemento en cuestión. La ventaja es que se
facilitan las condiciones de búsqueda, ya que siempre se encuentra
la clave.
 La cabecera y el centinela se pueden utilizar tanto en listas
enlazadas simples como en listas doblemente enlazadas.
 Pueden utilizarse para implementar tanto un TAD ListaOrdinal
como un TAD ListaCalificada.
 A modo de ejemplo, se muestra el método insertar en una
ListaCalificada de objetos Alumno, implementada con una lista
enlazada con cabecera y centinela.
Listas > 4. Otras implementaciones. 72

4.2. Listas enlazadas con cabecera y centinela.


public boolean insertar(int clave, Alumno dato) {
Nodo anterior = cabecera;
Nodo actual = cabecera.getSiguiente(); // Primer nodo no falso
centinela.setClave(clave); // Clave en el centinela
// Recorrer la lista buscando la clave
while (actual.getClave() < clave) {
anterior = actual;
actual = actual.getSiguiente();
}
if (actual == centinela || actual.getClave() > clave) {
// Insertar antes de actual
Nodo nuevo = new Nodo(clave, dato, actual);
anterior.setSiguiente(nuevo);
numElementos++;
return true;
} else {
return false;
}
Listas > 4. Otras Implementaciones. 73

4.3. Listas enlazadas reorganizables.


 La posición de los nodos varía en función de los accesos que se
realizan.
 Ejemplo de un algoritmo de lista reorganizable:
Cada vez que se accede a un nodo, éste pasa a ocupar la posición inicial
de la lista. La idea es que si se ha accedido a un nodo, hay más
probabilidad de que se vuelva a acceder al mismo.
 No se crean ni se destruyen nodos, sólo se reorganizan los
enlaces.
 Estos algoritmos se pueden aplicar tanto en listas enlazadas
simples como en listas doblemente enlazadas.
 Es válido utilizar una lista enlazada reorganizable para
implementar una ListaOrdinal, pero no una ListaCalificada, ya
que los nodos están ordenados por la clave.

También podría gustarte