Cap´ıtulo 3 Listas

Anuncio
Capı́tulo 3
Listas
3.1
Introducción
Las listas las tenemos en muchos casos en la vida real: lista de compras, lista
de útiles escolares, lista de invitados, lista de pendientes, lista de tareas, lista
de discos, etc. Pero, ¿qué es una lista? Una lista es una estructura de datos
con la propiedad que se pueden agregar y suprimir datos en cualquier lugar.
El tipo de datos Lista debe tener las siguientes propiedades:
• Determinar si está vacı́a la lista.
• Limpiar la lista.
• Agregar un elemento.
• Encontrar un elemento. (por valor).
• Actualizar un elemento.
• Eliminar un elemento.
Como caracterı́sticas de las listas se tiene que de antemano se desconoce
cuántos elementos tendrá ésta. Es irrelevante el orden de los elementos y el
manejo de ellos, es decir las inserciones y supresiones pueden ser de cualquier
dato no por posición.
La interfaz para la clase de las listas es:
1
3.2. IMPLEMENTACIÓN
2
interface Listable {
public boolean estáVacı́a();
public void limpiar();
public Object primerElemento();
public void agregar(Object x); //Inserta al inicio de la lista
public boolean contiene(Object x);
public void eliminar(Object x);
//Borra el primer nodo que tenga valor x
public void sustituir(Object orig, Object nuevo);
public java.util.Iterator elementos();
}
Propiedades que se esperan de una lista:
• estáVacı́a. Devuelve true si la lista está vacı́a y false en otro caso.
• limpiar. Elimina todos los elementos de la lista, es decir el tamaño de
la lista después de esta operación es cero..
• primerElemento. Devuelve el valor almacenado en el primer elemento
de la lista. El estado de la lista no se ve alterado.
• agregar. Agrega al inicio de la lista el objeto especificado como el
parámetro. El tamaño de la lista crece en una unidad.
• contiene. Regresa true si el elemento está en la lista y false en otro
caso. Este método no cambia el estado de la lista.
• eliminar. Si el elemento existe en la lista lo elimina, en caso contrario dispara la excepción NoSuchElementException. En caso de que la
operación sea exitosa se reduce el tamaño de la lista en una unidad.
• sustituir. Si el elemento a sustituir se encuentra en la lista lo sustituye por el segundo parámetro, en caso contrario dispara la excepción
NoSuchElementException. Esta operación no altera el tamaño de la
lista.
3.2
Implementación
Las listas se pueden implementar con arreglos pero para evitar el costo de
recorridos durante la inserción o supresión, se puede usar el concepto de lista
3.2. IMPLEMENTACIÓN
3
ligada (Ver figura) la cual consta de un conjunto de nodos, no necesariamente
almacenados en forma adyacente. Cada nodo contiene el elemento y una liga
o enlace a su sucesor.
e1
e2
e3
e4
e5
l
Figura 3.1:
Implementación usando dos clases: la de la lista ligada (Lista)y la de
nodos (Nodo). La clase de los nodos tinen un elemento que es el dato a guardar en la lista, y una referencia al siguiente nodo, que es la liga. Puede resultar
curioso que se tenga una referencia a un objeto del tipo que está definiendo
pero es válido hacer esto.
/*
* Clase para manejar los nodos de la lista
*/
class Nodo {
Object
elemento;
Nodo sgte;
/**
* Crea un nodo con elemento igual a valor y apuntando al vacı́o.
* @param valor el Objeto que es el valor de nodo
*/
Nodo(Object valor) {
elemento = valor;
sgte = null;
}
/**
* Crea un nodo después del indicado, con elemento igual a valor.
* @param valor el Objeto que es el valor de nodo
* @param n el nodo anterior al recién creado
*/
Nodo(Object valor, Nodo n) {
elemento = valor;
3.2. IMPLEMENTACIÓN
4
sgte
= n;
}
/*
* Devuelve el valor de un nodo
* @return Object el valor del nodo
*/
public Object daElemento () { return elemento; }
/**
* Devuelve la referencia del siguiente nodo
* @return NOdo el siguietne nodo
*/
public Nodo daSgte() { return sgte;}
}
A continuación la implementación de la interfaz, creando la lista ligada
con un nodo centinela al inicio de la misma.
/**
* Listada ligada usando un nodo centinela
*/
public class Lista implements Listable{
private Nodo inicio;
/** Construye la lista */
public Lista() {
inicio = null;
}
/** Prueba que la lista esté vacı́a.
* @return true si está vacı́a y false en otro caso.
*/
public boolean estaVacı́a() {
return inicio == null;
}
/** Crea una lista vacı́a. */
public void limpiarLista() {
inicio = null;
}
3.2. IMPLEMENTACIÓN
5
/** Devuelve el primer nodo de la lista **/
public Nodo primerElemento() {
return inicio;
}
/**
* Devuelve la posición del nodo que contiene el dato buscado.
* @param dato el dato a buscar.
* @return un nodo; null si el dato no se encuentra.
*/
public Nodo buscar(Object dato) {
Nodo pos = inicio;
while(pos != null && !pos.elemento.equals(dato))
pos = pos.sgte;
return pos;
}
public void sustituir(Object orig, Object nuevo)
Nodo n = buscar(orig);
if (n != null)
n.elemento = nuevo;
}
/**
* Inserta el primer elemento de la lista.
* @param dato el dato a agregar.
*/
public void agregar(Object dato) {
inicio = new Nodo(dato,inicio);
/**
* Elimina la primera ocurrencia de un dato.
* @param dato el dato a eliminar.
*/
public void eliminar(Object dato) {
Nodo pos = inicio, anterior = null;
{
while(pos != null && !pos.elemento.equals(dato)) {
anterior = pos;
pos = pos.sgte;
}
3.3. LISTAS CON DOS CENTINELAS
6
if (pos == null) return;
// No lo encontró
if(pos == inicio)
// Es el inicio de la lista
inicio = inicio.sgte
else
anterior.sgte = pos.sgte;
}
public java.util.Iterator elementos() {
return new MiIterador();
}
private class MiIterador implements java.util.Iterator {
private Nodo posicion = inicio;
public boolean hasNext() { return posicion != null;}
public Object next() throws java.util.NoSuchElementException {
if (hasNext()) {
Object o = posicion.elemento;
posicion = posicion.sgte;
return o;
}
throw new java.util.NoSuchElementException();
}
public void remove() {
throw new IllegalStateException();
}
}
}
3.3
Listas con dos centinelas
Con la implementación antes vista se tiene que la operación de agregar es de
O(1) lo cual es inmejorable. Sin embargo, si se presentara la situación que
la operación más común es la agregar al final de la lista, o suprimir el final
de la lista, esta serı́a una operación de orden n porque es necesario recorrer
toda la lista antes de llegar al final.
3.3. LISTAS CON DOS CENTINELAS
7
public void agregarFinal(Object dato) {
if (estaVacı́a())
agregar(dato);
else {
while(Nodo p = inicio; p.sgte != null; p = p.sgte)
;
p.sgte = new Nodo(dato);
}
Para que este método también sea de orden constante es necesario incluir
una nueva referencia que esté siempre apuntando al final de la lista. Es decir,
se puede hacer una implementación usando dos centinelas: el del inicio y y
otro al final de la lista. como se presenta aquı́:
/**
* Lista ligada usando dos centinelas: uno al inicio y otro al final.
*/
public class ListaF implements Listable {
private Nodo inicio;
private Nodo fin;
private final Comparator prueba;
Con lo cual la implementación de algunos métodos cambia y se incluyen
nuevos métodos.
/** Construye la lista */
public ListaF() {
inicio = null;
fin = null;
prueba = new DefaultComparator();
}
/** Devuelve el último elemento de la lista **/
public Object últimoElemento() {
return (estáVacı́a()) ? null : fin.elemento;
}
/**
* Inserta el primer elemento de la lista.
* @param dato el dato a agregar.
*/
3.4. LISTAS DOBLEMENTE LIGADAS
8
public void agregar(Object dato) {
if (estáVacı́a())
inicio = fin = new Nodo(dato);
else
inicio = new Nodo(dato,inicio);
}
/**
* Inserta el último elemento de la lista.
* @param dato el dato a agregar.
*/
public void agregarÚltimo(Object dato) {
if (estáVacı́a())
inicio = fin = new Nodo(dato);
else
fin = fin.sgte = new Nodo(dato);
}
/**
* Elimina el primer nodo de la lista.
*/
public void eliminarPrimero() {
inicio = inicio.sgte;
}
}
3.4
Listas doblemente ligadas
En ocasiones resulta conveniente poder moverse en ambas direcciones dentro
de la lista. En estos casos es conveniente que cada nodo cuente con un apuntador al nodo que le sigue y otro al nodo que le precede. Es decir se requiere
tener una lista con doble liga.
class NodoD {
Object
elemento;
NodoD sgte;
NodoD anterior;
/* Constructor de un nodo con valor dado y apuntadores a null.
* @param valor - Objeto que será el valor del nodo.
3.4. LISTAS DOBLEMENTE LIGADAS
9
*/
NodoD(Object valor) {
elemento = valor;
sgte = null;
anterior = null;
}
/* Constructor de un nodo con valor dado y apuntador siguiente al nodo dado
* El apuntador al nodo anterior es null.
* @param valor - Objeto que será el valor del nodo.
* @param sig - valor de la referencia siguiente
*/
NodoD(Object valor, NodoD sig) {
elemento = valor;
sgte
= sig;
anterior = null;
}
/* Constructor de un nodo con valor dado y apuntador al nodo siguiente y
* al anterior proporcionados.
* @param valor - Objeto que será el valor del nodo.
* @param ant - valor de la referencia anterior
* @param sig - valor de la referencia siguiente
*/
NodoD(Object valor, NodoD ant, NodoD sig) {
elemento = valor;
anterior = ant;
sgte
= sig;
}
}
Con estos nodos la implementación de la clase para listas ligadas quedarı́a
como sigue:
import java.util.Comparator;
/**
* Lista doblemente ligada con apuntador al inicio y al final de la misma.
* @version Octubre 2006
*/
public class ListaDoble implements Listable{
3.4. LISTAS DOBLEMENTE LIGADAS
10
private NodoD inicio;
private NodoD fin;
private final Comparator prueba;
/** Construye la lista vacı́a y utiliza el comparador por omisión */
public ListaDoble() {
inicio = fin = null;
prueba = new DefaultComparator();
}
/** Construye la lista con el comparador especificado */
public ListaDoble(Comparator c) {
inicio = fin = null;
prueba = c;
}
/** Prueba que la lista esté vacı́a.
* @return true si está vacı́a y false en otro caso.
*/
public boolean estáVacı́a() {
return inicio == null;
}
/** Crea una lista vacı́a. */
public void limpiar() {
inicio = fin = null;
}
/** Devuelve el primer elemento de la lista
* @return Object - primer elemento de la lista
*/
public Object primerElemento() {
return (estáVacı́a()) ? null : inicio.elemento;
}
/** Devuelve el último elemento de la lista **/
public Object últimoElemento() {
return (estáVacı́a()) ? null : fin.elemento;
}
/** Inserta el primer elemento de la lista
* @param dato - Objeto a agregar al inicio de la lista
*/
public void agregar(Object dato) {
3.4. LISTAS DOBLEMENTE LIGADAS
11
if (estáVacı́a())
inicio = fin = new NodoD(dato);
else
inicio = inicio.anterior = new NodoD(dato, inicio);
}
/** Inserta el último elemento de la lista
* @param x -- Objeto que se agregará al final de la lista
*/
public void agregarÚltimo(Object x) {
if (estáVacı́a())
agregar(x);
else {
NodoD n = new NodoD (x);
fin.sgte = n;
n.anterior = fin;
fin = fin.sgte;
}
}
/**
* Elimina la primera ocurrencia de un dato.
* @param dato el dato a eliminar.
*/
public void eliminar(Object dato) {
NodoD p = inicio;
while(p != null && prueba.compare(p.elemento,dato) != 0)
p = p.sgte;
if (p != null) return;
// No encontró el dato a eliminar
if (p == inicio)
eliminarPrimero();
else if (p == fin)
eliminarÚltimo();
else if(p.sgte != null) {
p.anterior.sgte = p.sgte;
p.sgte.anterior = p.anterior;
}
}
3.4. LISTAS DOBLEMENTE LIGADAS
12
/** Método para eliminar el primer elemento de una lista */
public void eliminarPrimero() {
if (!estáVacı́a()){
inicio = inicio.sgte;
if (inicio == null)
fin = null;
else
inicio.anterior = null;
}
}
/** Método para eliminar el último elemento de una lista
*/
public void eliminarÚltimo() {
if (!estáVacı́a()){
fin = fin.anterior;
if (fin == null)
inicio = null;
else
fin.sgte = null;
}
}
public MiIterador elementos()
{ return new MiIterador(); }
...
}
Faltarı́a hacer el iterador que incluya métodos para recorrer la lista en
sentido inverso.
Descargar