Universidad Carlos III de Madrid

Ingeniería de Telecomunicación

Enero-Mayo 2010 / January-May 2010

Prácticas de Colas

Lab Section1. Trabajo en el laboratorio

TimeTOTAL: 90 min

Exercise Section1.1. Implementación de una pila con listas enlazadas

Time50 min

Objetivo

Mediante la resolución de este ejercicio se pretende que el alumno aprenda a implementar una pila utilizando listas enlazadas como estructura de datos.

Apartado 1

Implementa una pila utilizando una lista enlazada como estructura de datos. La pila debe permitir almacenar cualquier tipo de objeto, e implementar al menos métodos para:

- Insertar un objeto en la pila (push).

- Recuperar un objeto de la pila (pop).

- Obtener el objeto de la cima (top) de la pila, sin extraerlo.

- Averiguar si hay algún objeto almacenado en la pila.

- Devolver el número de objetos almacenados en la pila.

Diseña la clase, decidiendo el nombre de cada método, sus parámetros y su valor de retorno. (Pista: Crea una clase Node, que sea la encargada de almacenar los valores de la pila)

Los nodos en una lista enlazada, como la que usaremos esta práctica, se enlazan unos con otros usando el atributo de la clase Node llamado next . Por otro lado, cada nodo, por separado, utiliza el atributo elem para apuntar al objeto que hay que guardar.

Apartado 2

Haz una clase de prueba que lea líneas de un fichero y las imprima en orden inverso en pantalla. Por ejemplo, si el fichero tiene cuatro lineas "A", "B", "C" y "D", debe imprimir "D", "C", "B" y "A". La solución debe utilizar internamente una pila.

Exercise Section1.2. Implementación de una cola con listas enlazadas

Time40 min

Objetivo

En este ejericicio, se pretende implementar una cola con una lista enlazada. Posteriormente, se añadirán métodos adicionales que permitirán añadir nueva funcionalidad para reforzar los conceptos sobre listas.

Una cola (queue) es una estructura de datos donde la inserción y eliminación de los elementos se hace según el principio FIFO (First In First Out). Esto es, el primer elemento en salir es el primero en entrar en la cola.

La cola se representa como una lista enlazada (también llamada lista encadenada). Uno de los extremos representa la cabeza de la cola (el nodo referenciado por top). El otro extremo representa la última posición de la cola (aquel nodo cuyo siguiente nodo es null). Por tanto, la extracción de la cola consiste en devolver el objeto asociado al nodo de la cabeza y desenlazarlo. Por el contrario, la inserción de un nuevo objeto consiste en localizar el último nodo y enlazar a continuación el nuevo nodo con dicho objeto. Para evitar tener que recorrer la lista siempre para encontrar el último nodo, es recomendable mantener un atributo que referencie al último nodo. Piense en la cola enlazada como la cola de la compra en que el tendero sabe quién es el primero y el último. Y cada uno de los clientes sabe quién va detrás de él, porque le ha dado la vez.

Los nodos en una lista enlazada, como la que usaremos esta práctica, se enlazan unos con otros usando el atributo de la clase Node llamado next. Por otro lado, cada nodo, por separado, utiliza el atributo info para apuntar al objeto que hay que guardar.

Como se puede apreciar, la clase LinkedQueue mantiene dos atributos, uno top, que apunta a la cabeza de la cola, por donde se extraen los elementos y otro, tail, por donde se introducen los elementos. De esta manera, se cumple la estructura FIFO.

  1. Vamos a modelar el proceso de recepción de pedidos en una empresa. Supongamos que la empresa sólo fabrica un producto; a la empresa llegan pedidos de distintos clientes; cada pedido contiene dos características: cliente y cantidad de producto; y los pedidos se procesan por orden de llegada. Se entrega la definición de la clase Order, que representa un pedido. Order.java

    public class Order {
            private String customer;
    	private int qtty;
    		public Order(int qtty, String customer) {
    			this.customer=customer;
    			this.qtty = qtty;
    		}
    		public void print (){
    			System.out.println("     Customer: " + this.getCustomer());
    			System.out.println("     Quantity: " + this.getQtty());
    			System.out.println("     ------------");
    		}
    		public int getQtty(){
    			return this.qtty;
    		}
    		public String getCustomer(){
    			return this.customer;
    		}
    }
    
    	  

    Programa la clase Queue, basada en una lista enlazada, que implemente la siguiente interfaz:

    public interface QueueInterface {
    
        // returns the number of elements in Queue
        public int size();                   
    
        // verifies if the Queue is empty. true if empty | false if not empty.
        public boolean isEmpty();
    
        // returns the first element in Queue. The element IS NOT REMOVED from Queue. Returns null if empty.
        public Object front();
    
        // adds a new element to the Queue.
        public void enqueue(Object info);
     
        // returns the first element in Queue. The element IS REMOVED from Queue. Returns null if empty.
        public Object dequeue();
    } 
    
    	

    Para ello, define la clase Node, que representa cada uno de los nodos de la lista enlazada. Cada nodo tendrá 2 referencias: una referencia al siguiente nodo de la lista (o null si es el último) y una referencia al objeto que realmente contiene.

    Construye una clase TestQueue con un main para probar la cola definida.

    Para poder probar mejor los desarrollos, añade un método printInfo() en la clase Queue, que presente en consola el número de elementos que contiene y recorra la cola presentando la información de cada uno de los elementos, usando el método print() de la clase Order. Es decir, que presente una información parecida a:

    ********* QUEUE DUMP *********                 
       Size: 4                                     
       ** Element 1                                
         Customer: cust1                           
         Quantity: 20                              
         ------------                              
       ** Element 2                                
         Customer: cust2                           
         Quantity: 30                              
         ------------                              
       ** Element 3                                
         Customer: cust3                           
         Quantity: 40                              
         ------------                              
       ** Element 4                                
         Customer: cust3                           
         Quantity: 50                              
         ------------                              
    ******************************                 
    

    Para imprimir los elementos de la lista, es necesario que el programa recorra la lista. Para ello debe comenzar por el primer nodo, pasar al segundo a través de su referencia next, después al tercero y así sucesivamente mientras no se llegue al final de la lista. El código se debería parecer a lo siguiente:

    Node node = top;
    while (node != null) {
      // print here the information of the node
      (...)
      node = node.getNext();
    }
    

    En el main, crea una nueva cola y crea al menos 3 pedidos. Añádelos a la cola y prueba los métodos front y dequeue. Para ver el efecto en cada caso, tras cada operación de inserción y extracción, invoca el método printInfo de la cola.

  2. Una vez tenemos cierta soltura con el manejo normal de una cola simple, vamos a complicarla un poco: Define un método para obtener el n-ésimo elemento de la cola (sin borrarlo). Debe devolver null si se pide una posición no válida. public Object getNth(int pos) Prueba a introducir cuatro elementos y obtener el tercero.

Homework Section2. Actividades para casa

Time110 min
Time60 min

Objetivo

Profundizar en el manejo de estructuras de datos listas.

Ejercicio

La clase Lista representa una colección lineal de datos organizados en forma de lista enlazada. Esta lista presenta la peculiaridad de que almacena una referencia a un nodo concreto de la lista, al que apunta la referencia posicion, sobre el cual se realizan las operaciones de inserción y extracción de datos.

La referencia posicion debe poder avanzar y retroceder por la lista posición a posición. Por ello, resulta adecuado que la lista está doblemente enlazada, esto es, que cada nodo guarde una referencia no sólo al nodo que lo sigue en la lista, sino también al nodo que lo precede. La siguiente figura ilustra la estructura de la lista con un ejemplo:

Las operaciones básicas que debe permitir realizar esta lista son las siguientes:

  1. Avanzar la referencia posicion: la referencia se mueve hacia delante tantas posiciones como se indique. Si la lista está vacía o el número de posiciones a avanzar no es mayor que cero, no se hace nada. Si se pide avanzar más posiciones de las que quedan en la lista, posicion avanzará sólo hasta el último nodo. Si posicion apunta a null antes de avanzar, se coloca en el primer nodo de la lista, y a continuación se avanza el número de posiciones indicado, descontando una (por ejemplo, si se pide avanzar 5 posiciones y posicion apunta a null inicialmente, primero se coloca apuntando al primer nodo, y después se avanzan 4 posiciones desde este).

  2. Retroceder la referencia posicion: la referencia posicion debe retroceder tantas posiciones como se indique. Si la lista está vacía o el número de posiciones a retroceder no es mayor que cero, no se hace nada. Si se pide retroceder más posiciones que el primer elemento de la lista, la referencia se quedará en este primer elemento.

  3. Insertar un nuevo dato en la lista: se inserta un nuevo nodo para guardar este dato, inmediatamente a continuación de posicion, esto es, el nodo nuevo seguirá al actualmente referenciado por posicion. Cuando posicion es null, el nuevo nodo se inserta en la primera posición de la lista. Al finalizar la inserción, la referencia posicion debe apuntar al nuevo nodo introducido.

  4. Extraer un dato de la lista: devuelve el dato del nodo apuntado por posicion, borra dicho nodo, y avanza posicion al siguiente nodo (queda apuntando a null si no hay siguiente nodo). Si posicion apunta a null, no se extrae nada y se devuelve null.

Programa la clase Nodo, la clase Lista y los siguientes métodos de ésta:

  1. public void avanzar(int posiciones) {...}

  2. public void retroceder(int posiciones) {...}

  3. public void insertar(Object dato) {...}

  4. public Object extraer() {...}

Time30 min

Objetivo

Aprender a trabajar con Deques(Bicolas).

Ejercicio

Una Deque, también conocida como Bicola, es una estructura lineal de datos que permite insertar y eliminar elementos por ambos extremos. Para su construcción, nos basaremos en el uso de una Lista Doblemente Enlazada (LDE) en la que implementaremos los métodos correspondientes para lograr dicho comportamiento.

El comportamiento de una Deque se puede lograr mediante la implementación de la siguiente interfaz que muestra sus métodos principales:

/** 
* Interface for a Deque. 
*  
* @author DIT-UC3M 
*  
*/  
   public interface Dqueue {  
   
      void insertFirst(Object obj);  
 
      void insertLast(Object obj);  
 
      Object removeFirst();  
 
      Object removeLast();  

      int size();  
   }

Para implemetar una Deque mediante una LDE realiza las siguientes tareas:

  1. Programa la clase DNode que constituye el nodo de una Lista Doblemente Enlazada (LDE). Dicho nodo contendrá las referencias prev y next a los nodos anterior y posterior de la misma, así como una referencia de tipo Object al dato que se almacena.

  2. Declara la clase DLDqueue que implementará una Deque basada en una LDE. Para ello, deberá implementar el interfaz Dqueue mencionado anteriormente.

  3. Añade los atributos head y tail que representan los nodos extremos de la LDE y el atributo entero size que permite almacenar el tamaño de la misma.

  4. Programa el constructor de la clase DLDqueue que inicializa las referencias head y tail a un nodo de la LDE que esté vacío y establece el tamaño a 0.

  5. Programa los siguientes métodos del interfaz Deque:

    • public void insertFirst(Object obj)

      Este método inserta un objeto al comienzo de la LDE.

    • public void insertLast(Object obj)

      Este método inserta un objeto al final de la LDE.

    • public Object removeFirst()

      Este método extrae un objeto del comienzo de la LDE y lo elimina de la lista. Si no existe dicho objeto, devuelve null.

    • public Object removeLast()

      Este método extrae un objeto del final de la LDE y lo elimina de la lista.Si no existe dicho objeto, devuelve null.

    • public int size()

      Este método devuelve el tamaño de la lista LDE.

  6. Por úlitmo, implementa el método public String toString() que permite imprimir el contenido de la Deque por pantalla con el siguiente formato (los valores que aparecen son de ejemplo):

    head [ 1-2-3-4-5 ] tail

¡¡ NOTA IMPORTANTE !!

En este ejercicio, hemos practicado el diseño y la implementación de una Deque. Sin embargo, en el API de Java existen clases que también implementan este tipo de estructuras de datos. Una Deque está definida por la siguiente interfaz: Interface Deque. Existen dos posibles implementaciones de la misma, una basada en arrays (ArrayDeque) y otra basada en Listas Enlazadas (LinkedList).

Time20 min

Objetivo

Aprender cómo hacer que una Deque se comporte como una Pila o una Cola.

Ejercicio

En este ejercicio, vamos a aprovechar la flexibilidad de una Deque para hacer que se comporte como si fueran una Pila o una Cola. Para hacerlo, obligaremos a que la clase que la use implemente el interfaz correspondiente que se muestra a continuación:

  • Interfaz de una Pila:

    /** 
    * Interface for a Stack. 
    *  
    * @author DIT-UC3M 
    *  
    */  
       public interface Stack {  
       
          void push(Object obj);  
      
          Object pop();  
    
          int size();  
       }
    
  • Interfaz de una Cola:

    /** 
    * Interface for a Queue. 
    *  
    * @author DIT-UC3M 
    *  
    */  
       public interface Queue {  
      
          public void enqueue(Object obj); 
    
         public Object dequeue();  
    
          int size();  
       }
    
    

A continuación, vamos a usar una Deque para implementar tanto una Pila como una Cola de datos. Para ello, realiza las siguientes tareas:

  • Para implemetar una Pila mediante una Dqueue:

    1. Declara la clase DQStack que se comportará como una Pila. Para ello deberá implementar el interfaz Stack mencionado anteriormente.

    2. Añade un atributo data que sea de tipo DLDqueue y que constituye la lista donde se almacenarán los datos de la pila.

    3. Programa el constructor de la clase DQStack que inicializa el atributo data creando un objeto de la clase DLDqueue.

    4. Programa los siguientes métodos del interfaz Stack:

      • public void push(Object obj)

        Este método inserta un objeto al comienzo de la DLDqueue.

      • public Object pop()

        Este método extrae un objeto del comienzo de la DLDqueue y lo elimina de la lista. Si no existe dicho objeto, devuelve null.

      • public int size()

        Este método devuelve el tamaño de la DLDqueue.

    5. Implementa el método main para probar la Pila. Deberá crear una instancia de una DQStack, apilar y desapilar una serie de datos imprimiendo por pantalla el contenido y el tamaño de la pila.

  • Para implemetar una Cola mediante una DLDqueue se pide:

    1. Declara la clase DQQueue que debe comportarse como una Cola. Para ello deberá implementar el interfaz Queue proporcionado.

    2. Añade un atributo data que sea de tipo DLDqueue y que constituye la lista donde se almacenarán los datos de la cola.

    3. Programa el constructor de la clase DQQueue que inicializa el atributo data creando un objeto de la clase DLDqueue.

    4. Programa los siguientes métodos del interfaz Queue:

      • public void enqueue(Object obj)

        Este método inserta un objeto al comienzo de la DLDqueue.

      • public Object dequeue()

        Este método extrae un objeto del final de la DLDqueue y lo elimina de la lista.Si no existe dicho objeto, devuelve null.

      • public int size()

        Este método devuelve el tamaño de la DLDqueue.

    5. Por último, implementa el método main para probar la Cola. Dicho método deberá crear una instancia de una DQQueue, encolar y desencolar una serie de datos imprimiendo por pantalla el contenido y el tamaño de la lista.