Introducción: una simple colección Implemente una clase denominada Lista. La clase deberá mantener una colección de números y proveer los siguientes i i t métodos: ét d public class Lista{ public void agregarAlFinal(int data){ ... } public void imprimirContenido(){ ... } public boolean estaContenido(int data){ ... } public boolean eliminar(int data){ ... } } Listas dinámicas simplemente enlazadas Franco Guidi Polanco Escuela de Ingeniería Industrial Pontificia Universidad Católica de Valparaíso, Chile f d@ [email protected] l Actualización: 17 de noviembre de 2010 Franco Guidi Polanco (PUCV-EII) Introducción: una simple colección 17-11-2010 2 Aproximación al uso de listas dinámicas Supongamos la clase Pontífice. Recuerda la clase que has implementado… más adelante la revisitaremos. public class Pontífice{ private String Nombre; private Pontifice sucesor; public Pontífice(String n Pontífice s){ nombre = n; sucesor = s; } public void setNombre(String n){ nombre = n; } public String p g g getNombre(){ (){ return nombre; } public void setSucesor(Pontífice s){ sucesor = s; ; } public Pontífice getSucesor(){ return sucesor; } Los objetos de esta clase tienen un nombre y una referencia a un sucesor. El sucesor es un objeto bj t de d la l misma clase: } Franco Guidi Polanco (PUCV-EII) 17-11-2010 3 Franco Guidi Polanco (PUCV-EII) 17-11-2010 4 Aproximación al uso de listas dinámicas Aproximación al uso de listas dinámicas Suponga que una aplicación tiene una variable de tipo Pontífice. Dado: “Pedro” Pontífice p2 = new Pontífice (“Lino” , null) Pontífice p1 = new Pontífice(“Pedro”, p2); Explique qué hace el siguiente código: Podemos imaginar que en la memoria existirán dos objetos relacionados de la siguiente forma: 1A04 1A04 30F1 “Pedro” 30F1 “Lino” “Lino” p1 System.out.println( p1.getNombre() ); Y el siguiente: null System.out.println( p1.getSucesor().getNombre() ); p1 Representaremos la situación anterior sin hacer explícitas las direcciones de memoria (excepto null): “Pedro” Y el siguiente: Pontifice aux = p1.getSucesor(); System.out.println( aux.getNombre() ); “Lino” p1 Franco Guidi Polanco (PUCV-EII) 17-11-2010 5 Franco Guidi Polanco (PUCV-EII) Aproximación al uso de listas dinámicas: recorrido Dado: “Pedro” 6 Aproximación al uso de listas dinámicas: recorrido Dado: “Lino” “Anacleto” ... “Pedro” “Benedicto XVI” “Anacleto” ... “Benedicto XVI” La búsqueda de un nombres se logra mediante el siguiente código: La impresión de todos los nombres se logra mediante el siguiente código: String buscado = ... // aquí se asigna un valor boolean está = false; Pontífice aux = p1; p while( aux != null ){ if( aux.getNombre().equals( buscado ) ) está = true; aux = aux.getSucesor(); aux getSucesor(); } if( está ) System.out.println( buscado + “ sí está” ); else System.out.println( buscado + “ no está” ); Pontífice aux = p1; while( aux != null ){ System out println( aux System.out.println( aux.getNombre() getNombre() ); aux = aux.getSucesor(); } 17-11-2010 “Lino” p1 p1 Franco Guidi Polanco (PUCV-EII) 17-11-2010 7 Franco Guidi Polanco (PUCV-EII) 17-11-2010 8 Esquema tentativo de una lista dinámica simplemente enlazada Listas dinámicas simplemente enlazadas Son estructuras dinámicas: se asigna memoria para los elementos de la lista en la medida que es necesario. Cada elemento se almacena en una variable dinámica denominada nodo. En la lista simplemente enlazada, cada nodo apunta all nodo d que contiene ell elemento l siguiente Nodos data data data data h d head null ListaEnlazada Franco Guidi Polanco (PUCV-EII) 17-11-2010 9 Los nodos de una lista contendrán datos del tipo declarado en la estructura del nodo. Por ejemplo: 10 Implementar la colección discutida al inicio, pero mediante una lista dinámica simplemente enlazada. Tipos primitivos (byte, int, boolean, char, etc.) Referencias a objetos En los siguientes ejemplos consideraremos el uso de listas de enteros, aunque las técnicas que serán d descritas it son aplicables li bl a cualquier l i otro t “tipo” “ti ” de d lista. 17-11-2010 17-11-2010 Implementación de una lista dinámica simplemente enlazada Datos contenidos en la lista Franco Guidi Polanco (PUCV-EII) Franco Guidi Polanco (PUCV-EII) 11 public class Lista{ public void agregarAlFinal(int data){ ... } public void imprimirContenido(){ ... } public boolean estaContenido(int data){ ... } public boolean eliminar(int data){ ... } } Franco Guidi Polanco (PUCV-EII) 17-11-2010 12 Diagrama de clases de una lista dinámica simplemente enlazada Una lista dinámica simplemente enlazada (versión preliminar) Diagrama de clases (lista de enteros): Declaración de la Lista: public class Lista{ private Nodo head = null; public void agregarAlFinal(int data){ ... } public void imprimirContenido(){ ... } public boolean estaContenido(int data){ ... } public boolean eliminar(int data){ ... } } 0..1 next Lista Nodo head agregarAlFinal(d:Data) estáContenido(d:Data):boolean eliminar(d:Data):boolean imprimirContenido() Franco Guidi Polanco (PUCV-EII) 0..1 data:int getData():int setNext(n:Nodo) getNext():Nodo Nodo(d:Data,n:Nodo) ( , ) 17-11-2010 13 Nodos en una lista dinámica simplemente enlazada Franco Guidi Polanco (PUCV-EII) 17-11-2010 Inserción en la lista Insertar elementos: public class Nodo{ private int data; private Nodo next; public Nodo(int d, Nodo n){ data = d; next = n; ; } public int getData(){ return data; } public Nodo getNext(){ return next; } public void setNext(Nodo n){ next = n; } 17-11-2010 Ejemplo: [ 25, 1, 14, 4 ] Al final de la lista 25 1 14 4 head null Manteniendo un orden 1 4 14 25 head null Al inicio i i i de d la l lista li t 4 14 h d head 15 Franco Guidi Polanco (PUCV-EII) 1 25 null } Franco Guidi Polanco (PUCV-EII) 14 17-11-2010 16 Inserción de elementos al final de la lista Inserción de elementos al final de la lista Caso 1: la lista está vacía (variable head contiene null) Caso 2 (general): la lista tiene al menos un elemento 25 h d head h d head null 25 17-11-2010 null aux public Lista{ ... public void agregarAlFinal(int dato){ ... head = new Nodo( dato dato, null ); ... } ... } Franco Guidi Polanco (PUCV-EII) 1 head null 1. ubicar último nodo de la lista (aquél cuya variable next contiene null)) 2. Instanciar el nuevo nodo con el contenido indicado 3. Asignar el nuevo nodo a la variable next del último nodo (asegurándose de que la variable next del nuevo nodo sea igual a null) 25 14 1 head null aux 17 Franco Guidi Polanco (PUCV-EII) Inserción de elementos al final de la lista aux.setNext(new Nodo(dato, null)); 17-11-2010 18 Recorrido de la lista Caso general: Método que imprime el contenido de la lista: public class Lista{ ... public void agregarAlFinal(int dato){ Nodo odo nuevo ue o = new e Nodo(dato, odo(dato, null); u ); if( head == null ) head = nuevo; else{ Nodo aux = head; while( aux.getNext() != null) aux = aux.getNext(); aux.setNext( nuevo ); } public class Lista{ ... public void imprimirContenido(){ p p (){ Nodo aux = head; while( aux != null ){ System.out.print( aux.getData() + "; " ); aux = aux.getNext(); } System.out.println(); ... } } ... } Franco Guidi Polanco (PUCV-EII) 17-11-2010 19 Franco Guidi Polanco (PUCV-EII) 17-11-2010 20 Búsqueda en la lista Eliminación de un elemento Retorna true si el elemento está contenido en la lista Requiere identificar el nodo a borrar. Caso 1: es el primer nodo de la lista public class Lista{ ... public boolean estaContenido(int data){ Nodo aux = head; while( aux != null ){ if( data == aux.getData() ) return true; aux = aux.getNext(); } return false; 25 1 14 4 head null head = head.getNext(); g (); 25 1 14 4 head Sin otras Si t referencias: f i candidato did t a eliminación li i ió (recolector de basura de Java) } null ll ... 1 } 14 head Franco Guidi Polanco (PUCV-EII) 17-11-2010 21 Eliminación de un elemento 1 14 4 head null aux.setNext(aux.getNext().getNext()); aux 25 1 14 4 head Sin otras referencias: candidato a eliminación (recolector de basura de Java) 25 1 head Franco Guidi Polanco (PUCV-EII) null 4 null 17-11-2010 null 17-11-2010 22 Eliminación de un elemento Caso 2 (general): no es el primer nodo de la lista 25 Franco Guidi Polanco (PUCV-EII) 4 23 public class Lista{ ... public boolean eliminar(int data){ if( head != null) if( head.getData() == data ){ head = head.getNext(); return true; }else{ Nodo aux = head; while( aux.getNext() != null ){ if( aux.getNext().getData() == data ){ aux.setNext( ( aux.getNext().getNext() () () ) ); return true; } g (); aux = aux.getNext(); } } return false; } ... } Franco Guidi Polanco (PUCV-EII) 17-11-2010 24 Simplificación de la implementación propuesta: uso de un nodo “fantasma” Lista dinámica simplemente enlazada con nodo fantasma En la implementación descrita se deben hacer excepciones al insertar y eliminar el nodo del comienzo de la lista. Lista vacía: public class Lista{ Nodo head; public Lista(){ head = new Nodo(0, null); } head El manejo j se simplifica i lifi sii se utiliza tili un nodo d “fantasma”: ... null Lista con elementos: Es un nodo siempre presente en la primera posición de la lista Su contenido es irrelevante (el valor u objeto contenido no forma parte de la lista). } V l irrelevante Valor i l t 25 1 4 head null Primer elemento de la lista Franco Guidi Polanco (PUCV-EII) 17-11-2010 25 Operación de inserción en la lista con nodo fantasma Franco Guidi Polanco (PUCV-EII) 17-11-2010 26 Eliminación del primer elemento en la lista con nodo fantasma La inserción del primer elemento de la lista entra en el caso general: La eliminación del primer elemento de la lista entra en el caso general: 1 head null aux Clase Lista aux aux.setNext(aux.getNext().getNext()); aux.setNext(new Nodo(dato, null)); 14 public void agregarAlFinal(int dato){ Nodo aux = head; while( hil ( aux.getNext() tN t() ! != null) ll) aux = aux.getNext(); aux.setNext( new Nodo(dato, null) ); } 1 4 head null Sin otras referencias: candidato a eliminación (recolector de basura de Java) } Franco Guidi Polanco (PUCV-EII) 17-11-2010 27 Franco Guidi Polanco (PUCV-EII) 17-11-2010 28 Eliminación del primer elemento en la lista con nodo fantasma (cont.) Mejora al procedimiento de inserción de elementos al final de la lista El procedimiento descrito anteriormente requiere que todas las veces sea encontrado el último elemento de la lista. Más conveniente: tener una variable de instancia que siempre p referencie al último elemento de la lista. public boolean eliminar(int data){ Nodo aux = head; while( hil ( aux.getNext() t t() != ! null ll ){ if( aux.getNext().getData() == data ){ aux.setNext( aux.getNext().getNext() ); ; return true; } aux = aux.getNext(); } Esto aplica a listas con o sin nodo fantasma (con pequeños cambios). cambios) return false; } Franco Guidi Polanco (PUCV-EII) 17-11-2010 29 Mejora al procedimiento de inserción de elementos al final de la lista (cont.) 17-11-2010 30 Mejora al procedimiento de inserción de elementos al final de la lista (cont.) La variable de instancia tail mantiene siempre la referencia al último elemento: Diagrama de clases: Lista 1..1 agregarAlFinal(d:Data) estáContenido(d:Data) á eliminar(d:Data):boolean imprimirContenido() Franco Guidi Polanco (PUCV-EII) head 1..1 1..1 tail Nodo data: int getData():Data tD t () D t setNext(n:Nodo) getNext():Nodo Nodo(d:Data,n:Nodo) head null public class Lista{ Nodo head, tail; public Lista(){ head = new Nodo(0, null); tail = head; } tail ... } Diagrama de objetos: Lista head 25 ghost:Nodo 1stNode:Nodo 2ndNode:Nodo data = 20 data = -1 17-11-2010 1 4 null tail tail Franco Guidi Polanco (PUCV-EII) head 31 Franco Guidi Polanco (PUCV-EII) 17-11-2010 32 Mejora al procedimiento de inserción de elementos al final de la lista (cont.) El método agregarAlFinal ya no requiere recorrer la lista para ubicar el último nodo: public void agregarAlFinal(int dato){ Nodo aux = new Nodo(dato, null) ; tail setNext( aux ); tail.setNext( tail = aux; Ejercicio 1: implemente el método: agregarEnOrden(int dato) Versión con nodo fantasma que recibe un entero y lo agrega como primer elemento. La varaible tail es actualizada después de la inserción. Notar que el procedimiento de eliminación debe actualizar la referencia tail si se remueve el último nodo de la lista. 17-11-2010 que recibe un entero y lo agrega en orden ascendente a la lista. Ejercicio 2: implemente el método agregarAlInicio(int dato) } Franco Guidi Polanco (PUCV-EII) Inserción en orden/al inicio 33 Resumen listas dinámicas simplemente enlazadas Útiles para guardar un número no predefinido de elementos. Distintas disciplinas para mantener los datos ordenados (y para removerlos). El acceso a los nodos es secuencial; el recorrido es en una sola dirección (Ejercicio: confrontar con arreglos) l ) Franco Guidi Polanco (PUCV-EII) 17-11-2010 34 Otras estructuras dinámicas: listas doblemente enlazadas Están diseñadas para un acceso fácil al nodo siguiente y al anterior. Cada nodo contiene dos referencias: una apuntando al nodo siguiente, y otra apuntando al nodo anterior. El acceso a los nodos sigue siendo secuencial. secuencial La técnica del nodo fantasma puede ser útil también en este tipo p de lista. 1 14 4 head null null ll Franco Guidi Polanco (PUCV-EII) 17-11-2010 35 Franco Guidi Polanco (PUCV-EII) 17-11-2010 36 Otras estructuras dinámicas: lista circular Cada nodo tiene un sucesor. Conceptualmente no hay “primer” ni “último” nodo. Sus elementos pueden ser visitados secuencialmente a partir de cualquier nodo. 25 1 14 4 h d head Franco Guidi Polanco (PUCV-EII) 17-11-2010 37