Los desarrolladores utilizan los arrays y las variantes de listas enlazadas para construir una gran variedad de estructuras de datos complejas. Este p�gina explora dos de esas estructuras: las Pilas, las Colas . Cuando presentemos los algoritmos lo haremos �ncamente en c�digo Java por motivos de brevedad.
�Pilas que "Recuerdan"
La Pila es una estrucutra de datos donde las inserciones y recuperaciones/borrados de datos se hacen en uno de los finales, que es conocido como el top de la pila. Como el �ltimo elemento insertado es el primero en recuperarse/borrarse, los desarrolladores se refieren a estas pilas como pilas LIFO (last-in, first-out).
Los datos se push (insertan) dentro y se pop (recuperan/borran) de la parte superior de la pila. La siguiente figura ilustra una pila con tres String cada uno insertado en la parte superior de la pila:

Como muestra la figura anterior, las pilas se construyen en memoria. Por cada dato insertado, el it�m superior anterior y todos los datos inferiores se mueven hacia abajo. Cuando llega el momento de sacar un �tem de la pila, se recpupera y se borra de la pila el �tem superior (que en la figura anterior se revela como "third").
Las pilas son muy �tiles en varios escenarios de programaci�n. Dos de los m�s comunes son:
- Pilas que contienen direcciones de retorno:
Cuando el c�digo llama a un m�todo, la direcci�n de la primera instrucci�n que sigue a la llamada se inserta en la parte superior de la pila de llamadas de m�todos del thread actual. Cuando el m�todo llamado ejecuta la instrucci�n return, se saca la direcci�n de la parte superior de la pila y la ejecuci�n contin�a en sa direcci�n. Si un m�todo llama a otro m�todo, el comportamiento LIFO de la pila asegura que la instrucci�n return del segundo m�todo transfiere la ejecuci�n al primer m�todo, y la del primer m�todo transfiere la ejecuci�n al c�digo que sigue al c�digo que llam� al primer m�todo. Como resultado una pila "recuerda" las direcciones de retorno de los m�todos llamados. - Pilas que contienen todos los par�metros del m�todo llamado y las variables locales:
Cuando se llama a un m�todo, la JVM reserva memoria cerca de la direcci�n de retorno y almacena todos los par�metros del m�todo llamado y las variables locales de ese m�todo. Si el m�todo es un m�todo de ejemplar, uno de los par�metros que almacena en la pila es la referencia this del objeto actual.
Es muy com�n implementar una pila utilizando un array uni-dimensional o una lista de enlace simple. En el escenario del array uni-dimensional, una variable entera, t�picamente llamada top, contiene el �ndice de la parte superior de la pila. De forma similar, una variable de referencia, tambi�n nombrada noramlmente como top, referencia el nodo superior del escenario de la lista de enlace simple.
He modelado mis implementaciones de pilas despu�s de encontrar la arquitectura del API Collections de Java. Mis implementaciones constan de un interface Stack para una m�xima flexibilidad, las clases de implementaci�n ArrayStack y LinkedListStack, y una clase de soporte FullStackException. Para facilitar su distribuci�n, he empaquetado estas clases en un paquete llamado com.javajeff.cds, donde cds viene de estructura de datos complejas. El siguiente listado presenta el interface Stack:
// Stack.java
package com.javajeff.cds;
public interface Stack {
boolean isEmpty ();
Object peek ();
void push (Object o);
Object pop ();
}
Sus cuatro m�todos determinan si la pila est� vac�a, recuperan el elemento superior sin borrarlo de la pia, situan un elemento en la parte superior de la pila y el �ltimo recuera/borra el elemento superior. Aparte de un constructor espec�fico de la implementaci�n, su programa �nicamente necesita llamar a estos m�todos.
El siguiente listado presenta una implementaci�n de un Stack basado en un array uni-dimensional:
// ArrayStack.java
package com.javajeff.cds;
public class ArrayStack implements Stack {
private int top = -1;
private Object [] stack;
public ArrayStack (int maxElements) {
stack = new Object [maxElements];
}
public boolean isEmpty () {
return top == -1;
}
public Object peek () {
if (top < 0)
throw new java.util.EmptyStackException ();
return stack [top];
}
public void push (Object o) {
if (top == stack.length - 1)
throw new FullStackException ();
stack [++top] = o;
}
public Object pop () {
if (top < 0)
throw new java.util.EmptyStackException ();
return stack [top--];
}
}
ArrayStack revela una pila como una combinaci�n de un �ndice entero privado top y variables de referencia de un array uni-dimensional stack. top identifica el elemento superior de la pila y lo inicializa a -1 para indica que la pila est� vac�a. Cuando se crea un objeto ArrayStack llama a public ArrayStack(int maxElements) con un valor entero que representa el n�mero m�ximo de elementos. Cualquier intento de sacar un elemento de una pila vac�a mediante pop() resulta en el lanzamiento de una java.util.EmptyStackException. De forma similar, cualquier intento de poner m�s elementos de maxElements dentro de la pila utilizando push(Object o) lanzar� una FullStackException, cuyo c�digo aparece en el siguiente listado:
// FullStackException.java
package com.javajeff.cds;
public class FullStackException extends RuntimeException {
}
Por simetr�a con EmptyStackException, FullStackException extiende RuntimeException. Como resultado no se necesita a�adir FullStackException a la clausula throws del m�todo.
El siguiente listado presenta una implementaci�n de Stack utilizando una lista de enlace simple:
// LinkedListStack.java
package com.javajeff.cds;
public class LinkedListStack implements Stack {
private static class Node {
Object o;
Node next;
}
private Node top = null;
public boolean isEmpty () {
return top == null;
}
public Object peek () {
if (top == null)
throw new java.util.EmptyStackException ();
return top.o;
}
public void push (Object o) {
Node temp = new Node ();
temp.o = o;
temp.next = top;
top = temp;
}
public Object pop () {
if (top == null)
throw new java.util.EmptyStackException ();
Object o = top.o;
top = top.next;
return o;
}
}
LinkedListStack revela una pila como una combinaci�n de una clase anidada privada de alto nivel llamada Node y una variable de referencia privada top que se inicialia a null para indicar una pila vac�a. Al contrario que su contrapartida del array uni-dimensional, LinkedListStack no necesita un constructor ya que se expande din�micamente cuando se ponen los �tems en la pila. As�, void push(Object o) no necesita lanzar una FullStackException. Sin embargo, Object pop() si debe chequear si la pila est� vac�a, lo que podr�a resultar en el lanzamiento de una EmptyStackException.
Ahora que ya hemos visto el interface y las tres clases que generan mis implementaciones de las pilas, juguemos un poco. El siguiente listado muestra casi todo el soporte de pilas de mi paquete com.javajeff.cds:
// StackDemo.java
import com.javajeff.cds.*;
class StackDemo {
public static void main (String [] args) {
System.out.println ("ArrayStack Demo");
System.out.println ("---------------");
stackDemo (new ArrayStack (5));
System.out.println ("LinkedListStack Demo");
System.out.println ("--------------------");
stackDemo (new LinkedListStack ());
}
static void stackDemo (Stack s) {
System.out.println ("Pushing \"Hello\"");
s.push ("Hello");
System.out.println ("Pushing \"World\"");
s.push ("World");
System.out.println ("Pushing StackDemo object");
s.push (new StackDemo ());
System.out.println ("Pushing Character object");
s.push (new Character ('C'));
System.out.println ("Pushing Thread object");
s.push (new Thread ("A"));
try {
System.out.println ("Pushing \"One last item\"");
s.push ("One last item");
}
catch (FullStackException e) {
System.out.println ("One push too many");
}
System.out.println ();
while (!s.isEmpty ())
System.out.println (s.pop ());
try {
s.pop ();
}
catch (java.util.EmptyStackException e) {
System.out.println ("One pop too many");
}
System.out.println ();
}
}
Cuando se ejecuta StackDemo, produce la siguiente salida:
ArrayStack Demo --------------- Pushing "Hello" Pushing "World" Pushing StackDemo object Pushing Character object Pushing Thread object Pushing "One last item" One push too many Thread[A,5,main] C StackDemo@7182c1 World Hello One pop too many LinkedListStack Demo -------------------- Pushing "Hello" Pushing "World" Pushing StackDemo object Pushing Character object Pushing Thread object Pushing "One last item" One last item Thread[A,5,main] C StackDemo@cac268 World Hello One pop too many
�Priorizar con Colas
La Cola es una estructura de datos donde la inserci�n de �tem se hace en un final (el fin de la cola) y la recuperaci�n/borrado de elementos se hace en el otro final (el inicio de la cola). Como el primer elemento insertado es el primero en ser recuperado, los desarrolladores se refieren a estas colas como estructuras FIFO (first-in, first-out).
Normalmente los desarrolladores tabajan con dos tipos de colas: lineal y circular. En ambas colas, la inserci�n de datos se realiza en el fin de la cola, se mueven hacia adelante y se recuperan/borran del incio de la cola. La siguiente figura ilustra las colas lineal y circular:

La cola lineal de la figura anterior almacena cuatro enteros, con el entero 1 en primer lugar. Esa cola est� llena y no puede almacenar m�s datos adicionales porque rear identifica la parte final de la cola. La raz�n de la posici�n vac�a, que identifica front, implica el comportamiento lineal de la cola. Inicialmente, front y rear identifican la posici�n m�s a la izquierda, lo que indica que la cola est� vac�a. Para almacenar el entero 1, rear avanza una posici�n hacia la derecha y almacena 1 en esa posici�n. Para recuperar/borrar el entero 1, front avanza una posici�n hacia la derecha.
| Nota: |
|---|
|
Para se�alar que la cola lineal est� vac�a, no necesita gastar una posici�n, aunque esta aproximaci�n algunas veces es muy conneniente. En su lugar asigne el mismo valor que indique una posici�n no existente a front y a rear. Por ejemplo, asumiendo una implementaci�n basada en un array uni-dimensional, front y rear podr�an contener -1. El �ndice 0 indica entonces la posici�n m�s a la izquierda, y los datos se insertar�n empezando en este �ndice.
Cuando rear identifique la posici�n m�s a la derecha, la cola lineal podr�a no estar llena porque front podr�a haber avanzado almenos una posici�n para recuperar/borrar un dato. En este esceario, considere mover todos los �tems de datos hacia la izquierda y ajuste la posici�n de front y rear de la forma apropiada para crear m�s espacio. Sin embargo, demasiado movimiento de datos puede afectar al rendimiento, por eso debe pensar cuidadosamente en los costes de rendimiento si necesita crear m�s espacio. |
La cola circular de la figura anterior tiene siete datos enteros, con el entero 1 primero. Esta cola est� llena y no puede almacenar m�s datos hasta que front avance una posici�n en sentido horario (para recuperar el entero 1) y rear avance una posici�n en la misma direci�n (para identificar la posici�n que contendr� el nuevo entero). Al igual que con la cola lineal, la razon de la posici�n vac�a, que identifica front, implica el comportamiento circular de la cola. Inicialmente, front y rear identifican la misma posici�n, lo que indica una cola vac�a. Entonces rear avanza una posici�n por cada nueva inserci�n. De forma similar, front avanza una posici�n por cada recuperaci�n/borrado.
Las colas son muy �tiles en varios escenarios de programaci�n, entre los que se encuentran:
- Temporizaci�n de Threads:
Una JVM o un sistema operativo subyacente podr�an establecer varias colas para coincidir con diferentes prioridades de los threads. La informaci�n del thread se bloquea porque todos los threads con una prioridad dada se almacenan en una cola asociada. - Trabajos de impresi�n:
Como una impresora normalmente es m�s lenta que un ordenador, un sistema operativo maneja los trabajos de impresi�n en un subsistema de impresi�n, que inserta esos trabajos de impresi�n en una cola. El primer trabajo en esa cola se imprime primero, y as� sucesivamente.
Los desarrolladores normalmente utilizan una array uni-dimensional para implementar una cola. Sin embargo, si tienen que co-existir m�ltiple colas o las inserciones en las colas deben ocurrir en posiciones distintas a la �ltima por motivos de prioridades, los desarrolladores suelen cambiar a la lista doblemente enlazada. Con un array uni-dimensional dos variables enteras (normalmente llamadas front y rear) contienen los �ndices del primer y �ltimo elemento de la cola, respectivamente. Mis implementaciones de colas lineales y circulares usan un array uni-dimensional y empiezan con el interface Queue que puede ver en el siguiente listado:
// Queue.java
package com.javajeff.cds;
public interface Queue {
void insert (Object o);
boolean isEmpty ();
boolean isFull ();
Object remove ();
}
Queue declara cuatro m�todos para almacenar un datos, determinar si la cola est� vac�a, determinar si la cola est� llena y recuperar/borrar un dato de la cola. Llame a estos m�todos (y a un constructor) para trabajar con cualquier implementaci�n de Queue.
El siguiente listado presenta una a implementaci�n de Queue de una cola lineal basada en un array uni-dimensional:
// ArrayLinearQueue.java
package com.javajeff.cds;
public class ArrayLinearQueue implements Queue {
private int front = -1, rear = -1;
private Object [] queue;
public ArrayLinearQueue (int maxElements) {
queue = new Object [maxElements];
}
public void insert (Object o) {
if (rear == queue.length - 1)
throw new FullQueueException ();
queue [++rear] = o;
}
public boolean isEmpty () {
return front == rear;
}
public boolean isFull () {
return rear == queue.length - 1;
}
public Object remove () {
if (front == rear)
throw new EmptyQueueException ();
return queue [++front];
}
}
ArrayLinearQueue revela que una cola es una combinaci�n de variables privadas front, rear, y queue. front y rear se inicializan a -1 para indicar una cola vac�a. Igual que el constructor de ArrayStack llama a public ArrayLinearQueue(int maxElements) con un valor entero que especifique el n�mero m�ximo de elementos durante la construcci�n de un objeto ArrayLinearQueue.
El m�todo insert(Object o) de ArrayLinearQueue lanza una FullQueueException cuando rear identifica el elemento final del array uni-dimensional. El c�digo de FullQueueException aparece en el siguiente listado:
// FullQueueException.java
package com.javajeff.cds;
public class FullQueueException extends RuntimeException {
}
El m�todo remove() de ArrayLinearQueue lanza una EmptyQueueException cuando los objetos front y rear son iguales. El siguiente listado presenta el c�digo de esta clase:
// EmptyQueueException.java
package com.javajeff.cds;
public class EmptyQueueException extends RuntimeException {
}
El siguiente listado presenta una implementaci�n de Queue para una cola circular basada en un array uni-dimensional:
// ArrayCircularQueue.java
package com.javajeff.cds;
public class ArrayCircularQueue implements Queue {
private int front = 0, rear = 0;
private Object [] queue;
public ArrayCircularQueue (int maxElements) {
queue = new Object [maxElements];
}
public void insert (Object o) {
int temp = rear;
rear = (rear + 1) % queue.length;
if (front == rear) {
rear = temp;
throw new FullQueueException ();
}
queue [rear] = o;
}
public boolean isEmpty () {
return front == rear;
}
public boolean isFull () {
return ((rear + 1) % queue.length) == front;
}
public Object remove () {
if (front == rear)
throw new EmptyQueueException ();
front = (front + 1) % queue.length;
return queue [front];
}
}
ArrayCircularQueue revela una implementaci�n, en terminos de variables privadas y un constructor, muy similar a ArrayLinearQueue. El m�todo insert(Object o) es interesante porque guarda el valor actual de rear antes de hacer que esa variable apunte a la siguiente posici�n. Si la cola circular est� llena, rear restaura su valor original antes de lanzar una FullQueueException. La restauraci�n de rear es necesaria porque front es igual a rear (en ese punto), y una subsecuente llamada a remove() resulta en la lanzamiento de una EmptyQueueException (incluso aunque la cola circular no est� vac�a).
Despu�s de estudiar el c�digo del interface y de varias clases que lo implementan bas�ndose en arrays uni-dimensionales, consideremos en el siguiente listado una aplicaci�n que demuestra las colas lineales y circulares:
// QueueDemo.java
import com.javajeff.cds.*;
class QueueDemo {
public static void main (String [] args) {
System.out.println ("ArrayLinearQueue Demo");
System.out.println ("---------------------");
queueDemo (new ArrayLinearQueue (5));
System.out.println ("ArrayCircularQueue Demo");
System.out.println ("---------------------");
queueDemo (new ArrayCircularQueue (6)); // Need one more slot because
// of empty slot in circular
// implementation
}
static void queueDemo (Queue q) {
System.out.println ("Is empty = " + q.isEmpty ());
System.out.println ("Is full = " + q.isFull ());
System.out.println ("Inserting \"This\"");
q.insert ("This");
System.out.println ("Inserting \"is\"");
q.insert ("is");
System.out.println ("Inserting \"a\"");
q.insert ("a");
System.out.println ("Inserting \"sentence\"");
q.insert ("sentence");
System.out.println ("Inserting \".\"");
q.insert (".");
try {
System.out.println ("Inserting \"One last item\"");
q.insert ("One last item");
}
catch (FullQueueException e) {
System.out.println ("One insert too many");
System.out.println ("Is empty = " + q.isEmpty ());
System.out.println ("Is full = " + q.isFull ());
}
System.out.println ();
while (!q.isEmpty ())
System.out.println (q.remove () + " [Is empty = " + q.isEmpty () +
", Is full = " + q.isFull () + "]");
try {
q.remove ();
}
catch (EmptyQueueException e) {
System.out.println ("One remove too many");
}
System.out.println ();
}
}
Cuando se ejecuta QueueDemo, se produce la siguiente salida:
ArrayLinearQueue Demo --------------------- Is empty = true Is full = false Inserting "This" Inserting "is" Inserting "a" Inserting "sentence" Inserting "." Inserting "One last item" One insert too many Is empty = false Is full = true This [Is empty = false, Is full = true] is [Is empty = false, Is full = true] a [Is empty = false, Is full = true] sentence [Is empty = false, Is full = true] . [Is empty = true, Is full = true] One remove too many ArrayCircularQueue Demo --------------------- Is empty = true Is full = false Inserting "This" Inserting "is" Inserting "a" Inserting "sentence" Inserting "." Inserting "One last item" One insert too many Is empty = false Is full = true This [Is empty = false, Is full = false] is [Is empty = false, Is full = false] a [Is empty = false, Is full = false] sentence [Is empty = false, Is full = false] . [Is empty = true, Is full = false] One remove too many