Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Manual de Estructura de Datos
Manual de Estructura de Datos
Autor:
Traductor: Juan Antonio Palos (Ozito)
Jeff
Introduccin
Estructuras de Datos y Algoritmos Bsicos
o Qu es una Estructura de Datos?
o Qu es un Algoritmo?
o Cmo se Representa un Algoritmo?
Flowchart
Pseudo cdigo
Arrays
o Arrays de Una Dimensin
Utilizar slo un Inicializador
Utilizar slo la Palabra Clave "new"
Utilizar la palabra clave "new" y un Inicializador
o Trabajar con un array un-dimensional
o Algoritmos de bsqueda-lineal, bsqueda-binaria y ordenacin de burbuja
Bsqueda Lineal
Bsqueda Binaria
Ordenacin de Burbuja
o Arrays de Dos Dimensiones
Utilizar slo un Inicializador
Utilizar slo la palabra clave "new"
Utilizar la palabra clave "new" y un inicializador
o trabajar con Arrays bi-dimensionales
o Algoritmo de Multiplicacin de Matrices
o Arrays Desiguales
o Los Arrays Java son Objetos
Listas Enlazadas
o Lista de Enlace Simple
o Los Algoritmos de Concatenacin e Inversin
o Lista Doblemente Enlazada
Algoritmo de Insercin-Ordenada
o Lista de Enlace Circular
o Listas Enlazadas frente a Arrays
Pilas y Colas
o Pilas que "Recuerdan"
o Priorizar con Colas
rboles
o Organizacin Jerrquica con rboles
o Recursin
Friesen
Introduccin
La ciencia informtica enfatiza dos tpicos importantes: las estructuras de datos y los algoritmos. Estos tpicos son importantes
porque las elecciones que usted haga para las estructuras de datos y los algoritmos de un programa afectarn al uso de la
memoria (las estructuras de datos) y al tiempo del procesador (los algoritmos que interactan con esas estructuras de datos).
Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relacin inversa entre la utilizacin de memoria y
el tiempo de CPU: cuanto menos memoria utiliza una estructura de datos, ms tiempo de CPU necesitan los algoritmos asociados
para procesar los tems de datos de la estructura, que son valores de tipos primitivos u objetos, mediante referencias. De igual
forma, cuanto ms memoria utilice una estructura de datos, menor tiempo de CPU necesitan los algoritmos asociados y el
procesamiento de los tems de datos es mucho ms rpido. En la siguiente figura aparece est relacin inversa.
Un ejemplo de la relacin inversa entre la utilizacin de memoria y el consumo de CPU implica las estructuras de datos de los
arrays unidimensionales y las listas doblemente enlazadas, y sus algoritmos de insercin/borrado. Para una lista de tems dada,
una array unidimensional ocupa menos memoria que una lista doblemente enlzada: este tipo de listas necesita asociar enlaces con
tems de datos para encontrar el predecesor y el sucesor, lo que requiere memoria extra. Por el contrario los algoritmos para
insertar/eliminar elementos en un array unidimensional son ms lentos que los algoritmos equivalentes de una lista doblemente
enlazada: insertar o borrar un tem en un array unidimensional requiere movimiento de tems de datos para poder tener un
elemento vaco para insertar o para borrar.
Qu es un Algoritmo?
Normalmente los algoritmos se asocian con estructuras de datos. Un algoritmo es una secuencia de instrucciones que realizan
una tarea en un periodo de tiempo finito. El algoritmo recibe cero o ms entradas, produce al menos una salida, consiste en
instrucciones claras y poco ambiguas, termina despus de un nmero finito de pasos, y es lo suficientemente bsico que una
persona puede llevar a cabo el algoritmo utilizando lpiz y papel. Por el contrario, un programa no es necesariamente finito: el
programa, como un servidor Web, podra no terminar nunca si no hay intervencin externa. Algunos ejemplos de algoritmos
asociados con estructuras de datos son: bsqueda-lineal, ordenacin-de-burbuja, bsqueda-binaria, concatenacin-de-listasenlazadas, etc.
Flowchart
Un flowchart es una representacin visual del flujo de control de un algoritmo. Esta representacin ilustra las sentencias que se
tienen que ejecutar, las decisiones que hay que tomar, el flujo lgico (para iteracciones y otros propsitos), y terminaciones que
indican los puntos de entrada y salida. En la siguiente figura puede ver los distintos smbolos que puede utilizar en un flowchart:
Cul es el aspecto de un flowchart? Supongamos que usted tiene un sencillo algoritmo que inicializa un contador a 0, lee
caracteres hasta que ve un carcter de nueva lnea (\n), incrementa el contador por cada carcter dgito ledo, e imprime el valor
del contador despus de que haya ledo el carcter de nueva lnea. En la siguiente figura puede ver el flowchart que ilustra el flujo
de control de este algoritmo:
Entre las ventajas de un flowchart se incluye su simplicidad y su habilidad para representar visualmente el flujo de control del
algoritmo. Los flowcharts tambin tienen desventajas:
Pseudocdigo
Una alternativa al flowchart es el pseudo cdigo: una representacin en modo texto de un algoritmo que se aproxima al cdigo
fuente final. El pseudo cdigo es til para una escritura rpida de representaciones de algoritmos. Como la sintaxis no es lo ms
importante, no hay reglas definidas para escribir pseudo cdigo. Considere el siguiente ejemplo:
DECLARE CHARACTER ch
DECLARE INTEGER count = 0
DO
READ ch
IF ch IS '0' THROUGH '9' THEN
count++
END IF
UNTIL ch IS '\n'
PRINT count
END
Este ejemplo representa el pseudo cdigo equivalente al flowchart de la figura anterior. Aunque localizar el flujo de control en
pseudocdigo puede costar un poco ms que en un flowchart, normalmente, escribir pseudo cdigo lleva menos tiempo que dibujar
un flowchart.
Nota:
En este tutorial se utiliza pseudocdigo para representar algoritmos.
Arrays
El array es una de las estructuras de datos ms ampliamente utilizada por su flexibilidad para derivar en complejas estructuras de
datos y su simplicidad. Empezaremos con una definicin: un array es una secuencia de elementos, donde cada elemento (un
grupo de bytes de memoria que almacenan un nico tem de datos) se asocia con al menos un ndice (entero no-negativo). Esta
definicin lanza cuatro puntos interesantes:
Cada elemento ocupa el mismo nmero de bytes; el nmero exacto depende del tipo de datos del elemento.
Todos los elementos son del mismo tipo.
Tendemos a pensar que los elementos de un array ocupan localizaciones de memoria consecutivas. Cuando veamos los
arrays bi-dimensionales descubrir que no es siempre as.
El nmero de ndices asociados con cada elemento es la dimensin del array
Nota:
Esta seccin se enfoca exclusivamente en arrays de una y dos dimensiones porque los arrays de
mas dimensiones no se utilizan de forma tan frecuente.
Arrays de Una Dimensin
El tipo de array ms simple tiene una dimensin: cada elemento se asocia con un nico ndice. Java proporciona tres tcnicas para
crear un array de una dimensin: usar slo un inicializador, usar slo la palabra clave new, y utilizar la palabra clave new con un
inicializador.
type variable_name '[' ']' '=' 'new' type '[' integer_expression ']' ';'
type '[' ']' variable_name '=' 'new' type '[' integer_expression ']' ';'
Para ambas sintaxis:
Truco:
Los desarrolladores Java normalmente sitan los corchetes cuadrados despus del tipo (int []
test_scores) en vez de despus del nombre de la variable (int test_scores []) cuando
declaran una variable array. Mantener toda la informacin del tipo en un nico lugar mejora la
lectura del cdigo.
El siguiente fragmento de cdigo utiliza slo la palabra clave new para crear un array uni-dimensional que almacena datos de un
tipo primitivo:
Cuidado:
Cuando se crea un array uni-dimensional basado en un tipo primitivo, el compilador requiere que
aparezca la palabra clave que indica el tipo primitivo en los dos lados del operador igual-a. De otro
modo, el compilador lanzar un error. Por ejemplo, int [] test_scores = new long [20]; es
ilegal porque las palabras claves int y long representan tipos primitivos incompatibles.
Los arrays uni-dimensionales de tipos primitivos almacenan datos que son valores primitivos. Por el contrario, los arrays unidimensiones del tipo referencia almacenan datos que son referencias a objetos. El siguiente fragmento de cdigo utiliza la palabra
clave new para crear una pareja de arrays uni-dimensionales que almacenan datos basados en tipo referencia:
No se necesita especificar el nmero de elementos entre los corchetes cuadrados porque el compilador cuenta el nmero
de entradas en el inicializador. new asigna la memoria para los elementos del array uni-dimensional y asigna cada una de
las entradas del inicializador a un elemento en orden de izquierda a derecha.
= asigna la referencia al array uni-dimensional a la variable variable_name.
Nota:
Un array uni-dimensional (o de ms dimensiones) creado con la palabra clave new con un
inicializador algunas veces es conocido como un array annimo.
El siguiente fragmento de cdigo utiliza la palabra clave new con un inicializador para crear un array uni-dimensional con datos
basados en tipos primitivos:
int [] test_scores declara una variable de array uni-dimensional (test_scores) junto con su tipo de variable (int
[]). El cdigo new int [] { 70, 80, 20, 30 } crea un array uni-dimensional asignando memoria para cuatro
elementos enteros consecutivos; y almacena 70 en el primer elemento, 80 en el segundo, 20 en el tercero, y 30 en el cuarto. La
referencia del array uni-dimensional se asigna a test_scores.
Cuidado:
No especifique una expresin entera entre los corchetes cuadrados del lado derecho de la
igualdad. De lo contrario, el compilador lanzar un error. Por ejemplo, new int [3] { 70, 80,
20, 30 } hace que el compilador lance un error porque puede determinar el nmero de
elementos partiendo del inicializador. Adems, la discrepancia est entre el nmero 3 que hay en
los corchetes y las cuatro entradas que hay en el inicializador.
La tcnica de crear arrays uni-dimensionales con la palabra clave new y un inicializador tambin soporta la creacin de arrays que
contienen referencias a objetos. El siguiente fragmento de codigo utiliza esta tcnica para crear una pareja de arrays unidimensionales que almacenan datos del tipo referencia:
String [] months = new String [] { "Jan", "Feb", "Mar", "Apr", "May", "Jun"
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
System.out.println (months [0]); // Output: Jan
// The following method call results in an ArrayIndexOutOfBoundsException
// because index equals the month's length
System.out.println (months [months.length]);
System.out.println (months [months.length - 1]); // Output: Dec
// The following method call results in an ArrayIndexOutOfBoundsException
// because index < 0
System.out.println (months [-1]);
Ocurre una situacin interesante cuando se asigna la referencia de una subclase de un array a una variable de array de la
superclase, porque un subtipo de array es una clase del supertipo de array. Si intenta asignar una referencia de un objeto de la
superclase a los elementos del array de la subclase, se lanza una ArrayStoreException. El siguiente fragmento
demuestra esto:
Cuidado:
Tenga cuidado cuando acceda a los elementos de un array porque podra recibir una
ArrayIndexOutOfBoundsException o una ArrayStoreException.
Algoritmos de bsqueda-lineal, bsqueda-binaria y ordenacin de burbuja
Los desarrolladores normalmente escribien cdigo para buscar datos en un array y para ordenar ese array. Hay tres algoritmos
muy comunes que se utilizan para realizar estas tareas.
Bsqueda Lineal
El algoritmo de bsqueda lineal busca en un array uni-dimensional un dato especfico. La bsqueda primero examina el elemento
con el ndice 0 y continua examinando los elementos sucesivos hasta que se encuentra el tem o no quedan ms elementos que
examinar. El siguiente pseudocdigo demuestra este algoritmo:
// LSearchDemo.java
class LSearchDemo
public
int
int
for
Bsqueda Binaria
Al igual que en la bsqueda lineal, el algoritmo de bsqueda binaria busca un dato determinado en un array uni-dimensional. Sin
embargo, al contrario que la bsqueda lineal, la bsqueda binaria divide el array en seccin inferior y superior calculando el ndice
central del array. Si el dato se encuentra en ese elemento, la bsqueda binaria termina. Si el dato es numricamente menor que el
dato del elemento central, la bsqueda binaria calcula el ndice central de la mitad inferior del array, ignorando la seccin superior y
repite el proceso. La bsqueda continua hasta que se encuentre el dato o se exceda el lmite de la seccion (lo que indica que el
dato no existe en el array). El siguiente pseudocdigo demuestra este algoritmo:
DECLARE
DECLARE
DECLARE
DECLARE
INTEGER
INTEGER
INTEGER
INTEGER
// BSearchDemo.java
class BSearchDemo {
public
int
int
int
int
Ordenacin de Burbuja
Cuando entra en juego la ordenacin de datos, la ordenacin de burbuja es uno de los algoritmos ms simples. Este algoritmo hace
varios pases sobre un array uni-dimensional. Por cada pase, el algoritmo compara datos adyacentes para determinar si
numricamente es mayor o menor. Si el dato es mayor (para ordenaciones ascendentes) o menor (para ordenaciones
descendientes) los datos se intercambian y se baja de nuevo por el array. En el ltimo pase, el dato mayor (o menor) se ha movido
al final del array. Este efecto "burbuja" es el origen de su nombre. El siguiente pseudocdigo dumuestra este algoritmo (en un
contexto de ordenacin ascendente):
// BSortDemo.java
class BSortDemo {
public static void main (String [] args) {
int i, pass;
int [] x = { 20, 15, 12, 30, -5, 72, 456 };
for (pass = 0; pass < = x.length - 2; pass++)
for (i = 0; i < = x.length - pass - 2; i++)
if (x [i] > x [i + 1]) {
int temp = x [i];
x [i] = x [i + 1];
x [i + 1] = temp;
}
for (i = 0; i < x.length; i++)
System.out.println (x [i]);
}
}
BSortDemo produce la siguiente salida:
-5
12
15
20
30
72
456
Aunque la ordenacin de burbuja es uno de los algoritmos de ordenacin ms simples, tambin es uno de los ms lentos. Entre los
algoritmos ms rpidos se incluyen la ordenacin rpida y la ordenacin de pila.
Truco:
Otro algoritmo muy utilizado para arrays uni-dimensionales copia los elementos de un array fuente
en otro array de destino. En vez de escribir su propio cdigo para realizar esta terea puede utilizar
el mtodo public static void arraycopy(Object src, int srcindex, Object dst, int
dstindex, int length) de la clase java.lang.System, que es la forma ms rpida de realizar
la copia.
dimensional est vaco. Cada inicializador de fila especifica cero o ms valores iniciales para las entradas de las columnas
de esa fila. Si no se especifican valores para esa fila, la fila est vaca.
= se utiliza para asignar la referencia del array bi-dimensional a variable_name.
El siguiente cdigo usa slo un inicialiador para crear un array bi-dimensional que almacena datos basados en un tipo primitivo:
bidimensional de dos filas por tres columnas, donde la primera fila contiene los datos 20.5, 30.6, y 28.3, y la segunda fila
contitne los datos -38.7, -18.3, y -16.2. Detrs de la escena, se asigna memoria y se inicializan esto datos. El operador
igual-a asigna la referencia del array bi-dimensional a temperatures. La siguiente figura ilustra el array bi-dimensional
resultante desde un punto de vista conceptual y de memoria.
type variable_name '[' ']' '[' ']' '=' 'new' type '[' integer_expression ']'
'[' ']' ';'
type '[' ']' '[' ']' variable_name '=' 'new' type '[' integer_expression ']'
'[' ']' ';'
En ambas sintaxis:
type especifica el tipo de cada elemento. Como es una variable de array bi-dimensional contiene una referencia a un
array bi-dimensional, su tipo es type [ ] [ ].
La palabra clave new seguida por type y por una expresin entera entre corchetes cuadrados, que indentifica el nmero
de filas. new asigna memoria para las filas del array uni-dimensional de filas y pone a cero todos los bytes de cada
elemento, lo que significa que cada elemento contiene una refeencia nula. Debe crear un array uni-dimensional de
columnas separado y asignarle su referencia cada elemento fila.
= se utiliza para asignar la referencia del array bi-dimensional a variable_name.
El siguiente fragmento de cdigo usa slo la palabra clave new para crear un array bi-dimensional que almacena datos basados
en un tipo primitivo:
de filas enrre un par de corchetes cuadrados. Si no se especifica ningun inicializador de fila, el array bi-dimensional est
vaco. Cada inicializador de fila especifica cero o ms valores iniciales para las columnas de esa fila.
= se utiliza para asignar la referencia del array bi-dimensional a variable_name.
El siguiente fragmento de cdigo usa la palabra clave new y un inicilizador para crear un array bi-dimensional que almacena datos
basados en un tipo primitivo:
Cuidado:
La multiplicacin de matrices requiere que el nmero de columnas de la matriz de la izquierda (A)
sea igual al de la matriz de la derecha (B). Por ejemplo, para multiplicar una matriz A de cuatro
columnas por fila por una matriz B (como en A x B), B debe contener exactamente cinco filas.
El siguiente pseudocdigo demuestra el algoritmo de multiplicacin de matrices:
//
//
//
//
//
__________
_____
_________________________
| 10 30 |
| 5 |
| 10 x 5 + 30 x 7 (260) |
|
| X |
| = |
|
| 20 40 |
| 7 |
| 20 x 5 + 40 * 7 (380) |
__________
_____
_________________________
[ 5, 7 ]
El pseudocdigo de arriba requiere tres bucles FOR para realizar la multiplicacin. El bucle ms interno multiplica una sla fila de la
matriz a por un sola columna de la matriz B y aade el resultado a un sola entrada de la matriz C. El siguiente listado presenta el
equivalente Java del pseudocdigo anterior:
// MatMultDemo.java
class MatMultDemo {
public static void main (String [] args) {
int [][] a =
int [][] b =
dump (a);
System.out.println ();
dump (b);
System.out.println ();
int [][] c = multiply (a, b);
dump (c);
}
static void dump (int [][] x) {
if (x == null) {
System.err.println ("array is null");
return;
}
// Dump the matrix's element values to the standard output device
// in a tabular order
for (int i = 0; i < x.length; i++) {
for (int j = 0; j < x [0].length; j++)
System.out.print(x [i][j] + " ");
System.out.println ();
}
}
static int [][] multiply (int [][] a, int [][] b) {
// =========================================================
// 1. a.length contains a's row count
//
// 2. a [0].length (or any other a [x].length for a valid x)
//
contains a's column count
//
// 3. b.length contains b's row count
//
A qu ciudades debera enviar los semitrailers para obtener el mximo ingreso? Para resolver este problema, primero
consideremos la tabla de la imagen anterior como una matriz de precios de cuatro filas por tres columnas. Luego construimos una
matriz de tres filas por una columna con las cantidades, que aparece abajo:
==
==
| 1250 |
|
|
| 400 |
|
|
| 250 |
==
==
Ahora que tenemos las dos matrices, simplemente multiplicamos la matriz de precios por la matriz de cantidades para producir una
matriz de ingresos:
==
| 10.00
|
| 11.00
|
| 8.75
|
| 10.50
==
8.00
8.50
6.90
8.25
==
12.00 |
|
11.55 |
|X
10.00 |
|
11.75 |
==
==
==
| 1250 |
|
|
| 400 | =
|
|
| 250 |
==
==
==
==
| 18700.00 |
|
|
| 20037.50 |
|
|
| 16197.50 |
|
|
| 19362.50 |
==
==
New York
Los Angeles
Miami
Chicago
Enviar los dos semitrailers a Los Angles produce el mayor ingreso. Pero cuando se consideren la distancia y el consumo de gasoil,
quizs New York sea la mejor apuesta
Arrays Desiguales
Suponga que su cdigo fuente contiene la siguiente declaracin de matriz: int [][] x = new int [5][];. Esto
declara una matriz de enteros que contiene cinco filas, y x.length devuelve ese nmero de filas. Normalmente, completa la
creacin de la matriz especificando el mismo nmero de columnas para cada fila. Por ejemplo, especificando 10 columnas para
cada fila utilizando el siguiente cdigo:
x
x
x
x
x
[0]
[1]
[2]
[3]
[4]
=
=
=
=
=
new
new
new
new
new
int
int
int
int
int
[3];
[2];
[3];
[5];
[1];
Despus de ejecutar este cdigo, usted tendr una matriz degenerada conocida como un array desigual. La siguiente imagen
ilustra este tipo de arrays:
Los arrays desiguales son estructuras de datos tiles debido a su capacidad de ahorro de memoria. Por ejemplo, considere una
hoja de clculo con el potencial de 100.000 filas por 20.000 columnas. Si intentamos utilizar una matriz que contenga toda la hoja
de calculo, requeriremos una enorme cantidad de memoria. Pero supongamos que la mayora de la celdas contienen valores por
defecto, como un 0 para valores numricos y null para celdas no numricas. Si utilizamos un array desigual en lugar de una
matriz, almacenaremos slo las celdas que contienen datos numricos. (Por supuesto, necesitamos algn tipo de mecanismo de
mapeo que mapee las coordenadas de la hoja de clculo [filas, columnas] a las coordenadas del array desigual [filas], [columnas]).
// ArrayIsObject.java
class ArrayIsObject {
public static void main (String [] args) {
double [] a = { 100.5, 200.5, 300.5 };
double [] b = { 100.5, 200.5, 300.5 };
double [] c = b;
System.out.println ("a's class is " + a.getClass ());
System.out.println ("a and b are " + ((a.equals (b)) ? "" : "not ") +
"equal");
System.out.println ("b and c are " + ((b.equals (c)) ? "" : "not ") +
"equal");
double [] d = (double []) c.clone ();
System.out.println ("c and d are " + ((c.equals (d)) ? "" : "not ") +
"equal");
for (int i = 0; i < d.length; i++)
System.out.println (d [i]);
}
}
Truco:
En el cdigo fuente, especifique siempre .length (como d.length) en vez de la longitud real del
array. De esta forma, eliminar el riesgo de introducir bugs relacionados con la longitud, si
despus decide modificar la longitud del array en su cdigo de creacin.
Listas Enlazadas
Adems de los arrays, otra de las estructuras de datos muy utilizada es la lista enlazada. Esta estructura implica cuatro conceptos:
clase auto-refenciada, nodo, campo de enlace y enlace.
Clase auto-referenciada: una clase con al menos un campo cuyo tipo de referencia es el nombre de la clase:
class Employee {
private int empno;
private String name;
private double salary;
public Employee next;
// Other members
}
Employee es una clase auto-referenciada porque su campo next tiene el tipo Employee.
Los cuatro conceptos de arriba nos llevan a la siguiente definicin: una lista enlazada es una secuencia de nodos que se
interconectan mediante sus campos de enlace. En ciencia de la computacin se utiliza una notacin especial para ilustrar las listas
enlazadas. En la siguiente imagen aparece una variante de esta notacin que utilizar a lo largo de esta seccin:
La figura anterior presenta tres nodos: A, B y C. Cada nodo se divide en reas de contenido (en naranja) y una o ms reas de
enlace (en verde). Las reas de contenido representan todos los campos que no son enlaces, y cada rea de enlace representa un
campo de enlace. Las reas de enlace de A y C tienen unas flechas para indicar que referencian a otro nodo del mismo tipo (o
subtipo). El nico rea de enlace de B incorpora una X para indicar una referencia nula. En otras palabras, B no est conectado a
ningn otro nodo.
Aunque se pueden crear muchos tipos de listas enlazadas, las tres variantes ms populares son la lista de enlace simple, la lista
doblemente enlazada y la lista enlazada circular. Exploremos esas variantes, empezando con la lista enlazada.
Un algoritmo comn de las listas de enlace simple es la insercin de nodos. Este algoritmo est implicado de alguna forma porue
tiene mucho que ver con cuatro casos: cuando el nodo se debe insertar antes del primer nodo; cuando el nodo se debe insertar
despus del ltimo nodo; cuando el nodo se debe insertar entre dos nodos; y cuando la lista de enlace simple no existe. Antes de
estudiar cada caso consideremos el siguiente pseudocdigo:
top.name = "A"
top.next = NULL
En la siguiente imagen se puede ver la lista de enlace simple que emerge del pseudocdigo anterior:
El siguiente listado presenta el equivalente Java de los ejemplos de pseudo cdigo de insercin anteriores:
// SLLInsDemo.java
class SLLInsDemo {
static class Node {
String name;
Node next;
}
public static void main (String [] args) {
Node top = null;
// 1. The singly linked list does not exist
top = new Node ();
top.name = "A";
top.next = null;
dump ("Case 1", top);
// 2. The singly linked list exists, and the node must be inserted
//
before the first node
Node temp;
temp = new Node ();
temp.name = "B";
temp.next = top;
top = temp;
dump ("Case 2", top);
// 3. The singly linked list exists, and the node must be inserted
//
after the last node
temp = new Node ();
temp.name = "C";
temp.next = null;
Node temp2;
temp2 = top;
while (temp2.next != null)
temp2 = temp2.next;
temp2.next = temp;
dump ("Case 3", top);
// 4. The singly linked list exists, and the node must be inserted
//
between two nodes
temp = new Node ();
temp.name = "D";
temp2 = top;
while (temp2.name.equals ("A") == false)
temp2 = temp2.next;
temp.next = temp2.next;
temp2.next = temp;
dump ("Case 4", top);
}
static void dump (String msg, Node topNode) {
System.out.print (msg + " ");
while (topNode != null) {
System.out.print (topNode.name + " ");
topNode = topNode.next;
}
System.out.println ();
}
}
El mtodo static void dump(String msg, Node topNode) itera sobre la lista e imprime su contenido. Cuando
se ejecuta SLLInsDemo, las repetidas llamadas a este mtodo dan como resultado la siguiente salida, lo que coincide con las
imagnes anteriores:
Case
Case
Case
Case
1
2
3
4
A
B A
B A C
B A D C
Nota:
SLLInsDemo y los ejemplos de pseudocdigo anteriores empleaban un algoritmo de bsqueda
lineal orientado a listas enlazadas para encontrar un Node especfico. Indudablemente usted
Node temp = top // We use temp and not top. If top were used, we
// couldn't access the singly linked list after
// the search finished because top would refer
// to the final Node.
WHILE temp.next IS NOT NULL
temp = temp.next
END WHILE
// temp now references the last Node.
Bsqueda de un Node especfico:
// Assume top references a singly linked list of at least one Node.
Node temp = top
WHILE temp IS NOT NULL AND temp.name IS NOT "A" // Search for "A".
temp = temp.next
END WHILE
// temp either references Node A or contains NULL if Node A not found.
Otro algoritmo comn de las listas de enlace simples es el borrado de nodos. Al contrario que la inserccin de nodos, slo hay dos
casos a considerar:
top = top.next; // Reference the second Node (or NULL if there is only
one Node)
La siguiente imagen presenta las vistas anterior y posterior de una lista donde se ha borrado el primer nodo. en esta
figura, el nodo B desaparece y el nodo A se convierte en el primer nodo.
temp = top
WHILE temp.name IS NOT "A"
temp = temp.next
END WHILE
// We assume that temp references Node A
temp.next = temp.next.next
// Node D no longer exists
La siguiente figura presenta las vistas anterior y posterior de una lista donde se ha borrado un nodo intermedio. En esa
figura el nodo D desaparece.
// SLLDelDemo.java
class SLLDelDemo {
static class Node {
String name;
Node next;
}
public static void main (String [] args) {
// Build Figure 6's singly linked list (i.e., B A D C)
Node top = new Node ();
top.name = "C";
top.next = null;
Node temp = new Node ();
temp.name = "D";
temp.next = top;
top = temp;
temp = new Node ();
temp.name = "A";
temp.next = top;
top = temp;
Cuidado:
Como java inicializa los campos de referencias de un objeto a null durante la construccin del
objeto, no es necesario asignar explcitamente null a un campo de enlace. No olvide estas
asignaciones de null en su cdigo fuente; su ausencia reduce la claridad del cdigo.
Despus de estudiar SLLDelDemo, podra preguntarse qu sucede si asigna null al nodo referenciado por top: el
recolector de basura recoger toda la lista? Para responder a esta cuestin, compile y ejecute el cdigo del siguiente listado:
// GCDemo.java
class GCDemo {
static class Node {
String name;
Node next;
protected void finalize () throws Throwable {
System.out.println ("Finalizing " + name);
super.finalize ();
}
}
>
// CIDemojava
class CIDemo {
static class DictEntry {
String word;
String meaning;
DictEntry next;
}
// ListInfo is necessary because buildList() must return two pieces
// of information
static class ListInfo {
DictEntry top;
DictEntry last;
}
public static void main (String [] args) {
String [] wordsMaster = { "aardvark", "anxious", "asterism" };
ListInfo liMaster = new ListInfo ();
buildList (liMaster, wordsMaster);
dump ("Master list =", liMaster.top);
String [] wordsWorking = { "carbuncle", "catfish", "color" };
ListInfo liWorking = new ListInfo ();
buildList (liWorking, wordsWorking);
dump ("Working list =", liWorking.top);
// Perform the concatenation
liMaster.last.next = liWorking.top;
dump ("New master list =", liMaster.top);
invert (liMaster);
dump ("Inverted new master list =", liMaster.top);
}
static void buildList (ListInfo li, String [] words) {
if (words.length == 0)
return;
// Create a node for first word/meaning
li.top = new DictEntry ();
li.top.word = words [0];
li.top.meaning = null;
// Initialize last reference variable to
// simplify append and make concatenation possible.
li.last = li.top;
for (int i = 1; i < words.length; i++) {
// Create (and append) a new node for next word/meaning
li.last.next = new DictEntry ();
li.last.next.word = words [i];
li.last.next.meaning = null;
// Advance last reference variable to simplify
// append and make concatenation possible
li.last = li.last.next;
}
li.last.next = null;
}
static void dump (String msg, DictEntry topEntry) {
System.out.print (msg + " ");
while (topEntry != null) {
System.out.print (topEntry.word + " ");
topEntry = topEntry.next;
}
System.out.println ();
}
static void invert (ListInfo li) {
DictEntry p = li.top, q = null, r;
while (p != null) {
r = q;
q = p;
p = p.next;
q.next = r;
}
li.top = q;
}
}
CIDemo declara un DictEntry anidado en la clase de ms alto nivel cuyos objetos contienen palabras y significados. (Para
mentener el programa lo ms sencillo posible, he evitado los significados. Usted puede aadirlos si lo desea). CIDemo tambin
declara ListInfo para seguir las referencias el primero y ltimo DictEntry de una lista de enlace simple.
El thread principal ejecuta el mtodo public static void main(String [] args) de CIDemo. Este thread
llama dos veces al mtodo static void buildList (ListInfo li, String [] words) para crear dos
listas de enlace simple: una lista maestra (cuyos nodos se rellenan con palabras del array wordsMaster), y una lista de trabajo
(cuyos nodos se rellenan con palabras del array wordsWorking). Antes de cada llamada al mtodo buildList
(ListInfo li, String [] words), el thread principal crea y pasa un objeto ListInfo. este objeto devuelve las
referencias al primero y ltimo nodo. (Una llamada a mtodo devuelve directamente un slo dato). Despus de construir una lista
de enlace simple, el thread principal llama a static void dump (String msg, DictEntry topEntry) para
volcar un mensaje y las palabras de los nodos de una lista en el dispositivo de salida estndar.
Se podra estar preguntando sobre la necesidad del campo last de ListInfo. Este campo sirve a un doble propsito:
primero, simplifica la creacin de cada lista, donde se aaden los nodos. Segundo, este campo simplifica la concatenacin, que se
queda slo en la ejecucin de la siguiente lnea de cdigo: liMaster.last.next = liWorking.top;. Una vez que
se completa la concatenacin, y el thread principal vuelva los resultados de la lista maestra en la salida estndar, el thread llama al
mtodo static void invert (ListInfo li) para invertir la lista maestra y luego muestra la lista maestra invertida
por la salida estndar.
Cuando ejecute CIDemo ver la siguiente salida:
topForward referencia el primer nodo en la direccion hacia adelante, y topBackward referencia el primero nodo la
direccin inversa.
Truco:
Piense en una lista doblemente enlazada como una pareja de listas de enlace simple que
interconectan los mismos nodos.
La insercin y borrado de nodos en una lista doblemente enlazada son operaciones comunes. Estas operaciones se realizan
mediante algoritmos que se basan en los algoritmos de insercin y borrado de las listas de enlace simple (porque las listas
doblemente enlazadas slo son una pareja de listas de enlace simple que interconectan los mismos nodos).
El siguiente listado muestra la insercin de nodos para crear la lista de la figura anterior, el borrado de nodos ya que elimina el nodo
B de la lista, y el movimiento por la lista en ambas direcciones:
// DLLDemo.java
class DLLDemo {
static class Node {
String name;
Node next;
Node prev;
}
public static void main (String [] args) {
// Build a doubly linked list
Node topForward = new Node ();
topForward.name = "A";
Node temp = new Node ();
temp.name = "B";
Node topBackward = new Node ();
topBackward.name = "C";
topForward.next = temp;
temp.next = topBackward;
topBackward.next = null;
topBackward.prev = temp;
temp.prev = topForward;
topForward.prev = null;
// Dump forward singly linked list
System.out.print ("Forward singly-linked list: ");
temp = topForward;
while (temp != null){
System.out.print (temp.name);
temp = temp.next;
}
System.out.println ();
// Dump backward singly linked list
System.out.print ("Backward singly-linked list: ");
temp = topBackward;
while (temp != null){
System.out.print (temp.name);
temp = temp.prev;
}
System.out.println ();
// Reference node B
temp = topForward.next;
// Delete node B
temp.prev.next = temp.next;
temp.next.prev = temp.prev;
// Dump forward singly linked list
System.out.print ("Forward singly-linked list (after deletion): ");
temp = topForward;
while (temp != null){
System.out.print (temp.name);
temp = temp.next;
}
System.out.println ();
// Dump backward singly linked list
System.out.print ("Backward singly-linked list (after deletion): ");
temp = topBackward;
while (temp != null){
System.out.print (temp.name);
temp = temp.prev;
}
System.out.println ();
}
}
Cuando se ejecuta, DLLDemo produce la siguiente salida:
Algoritmo de Insercin-Ordenada
Algunas veces querr crear una lista doblemente enlazada que organice el orden de sus nodos basndose en un campo no de
enlace. Atravesar la lista doblemente enlazada en una direccin presenta esos nodos en orden ascendente, y atravesarla en en
direccin contraria los presenta ordenados descedentemente. El algoritmo de ordenacin de burbuja es inapropiado en este caso
porque requiere ndices de array. Por el contrario, insercin-ordenada construye una lista de enlace simple o una lista doblemente
enlzada ordenadas por un campo no de enlace para identificar el punto de insercin de cada nuevo nodo. El siguiente litado
demuestra el algoritmo de insercin-ordenada:
// InsSortDemo.java
class InsSortDemo {
// Note: To keep Employee simple, I've omitted various constructor and
// nonconstructor methods. In practice, such methods would be present.
static class Employee {
int empno;
String name;
Employee next;
Employee prev;
}
public static void main (String [] args) {
// Data for a doubly linked list of Employee objects. The lengths of
// the empnos and names arrays must agree.
int [] empnos = { 687, 325, 567, 100, 987, 654, 234 };
String [] names = { "April", "Joan", "Jack", "George", "Brian", "Sam",
"Alice" };
Employee topForward = null;
Employee topBackward = null;
// Prime the doubly linked list by creating the first node.
topForward = new Employee ();
topForward.empno = empnos [0];
topForward.name = names [0];
topForward.next = null;
topForward.prev = null;
topBackward = topForward;
// Insert remaining Employee nodes (in ascending order -- via empno)
// into the doubly linked list.
for (int i = 1; i < empnos.length; i++) {
// Create and initialize a new Employee node.
Employee e = new Employee ();
e.empno = empnos [i];
e.name = names [i];
e.next = null;
e.prev = null;
// Locate the first Employee node whose empno is greater than
// the empno of the Employee node to be inserted.
Employee temp = topForward;
while (temp != null && temp.empno <= e.empno)
temp = temp.next;
// temp is either null (meaning that the Employee node must be
// appended) or not null (meaning that the Employee node must
// be inserted prior to the temp-referenced Employee node).
if (temp == null) {
topBackward.next = e; // Append new Employee node to
// forward singly linked list.
e.prev = topBackward; // Update backward singly linked
topBackward = e;
// list as well.
}
else{
if (temp.prev == null) {
e.next = topForward; // Insert new Employee node at
topForward = e;
// head of forward singly linked
// list.
temp.prev = e;
// Update backward singly linked
// list as well.
}
else {
e.next = temp.prev.next; // Insert new Employee node
temp.prev.next = e;
// after last Employee node
// whose empno is smaller in
// forward singly linked list.
e.prev = temp.prev;
// Update backward
temp.prev = e;
//singly linked list as well.
}
}
}
// Dump forward singly linked list (ascending order).
System.out.println ("Ascending order:\n");
Employee temp = topForward;
while (temp != null) {
System.out.println ("[" + temp.empno + ", " + temp.name + "] ");
temp = temp.next;
}
System.out.println ();
// Dump backward singly linked list (descending order).
System.out.println ("Descending order:\n");
temp = topBackward;
while (temp != null) {
System.out.println ("[" + temp.empno + ", " + temp.name + "] ");
temp = temp.prev;
}
System.out.println ();
}
}
InsSortDemo simplifica su operacin creando primero un nodo Employee primario. Para el resto de nodos Employee,
InsSortDemo localiza la posicin de insercin apropiada basndose en el campo no de enlace empno, y luego inserta el
Employee en esa posicin. Cuando ejecute InsSortDemo, podr observar la siguiente salida:
Ascending order:
[100, George]
[234, Alice]
[325, Joan]
[567, Jack]
[654, Sam]
[687, April]
[987, Brian]
Descending order:
[987, Brian]
[687, April]
[654, Sam]
[567, Jack]
[325, Joan]
[234, Alice]
[100, George]
Tanto la insercin-ordenada como la ordenacin de burbuja exhiben prcticamente el mismo rendimiento.
Las listas de enlace circular se utilizan con frecuencia en procesamiento repetitivo de nodos en un orden especfico. Dichos nodos
podran representar conexiones de servidor, procesadores esperando una seccin crtica, etc. Esta estructura de datos tambin
sirve como base para una variante de una estructura de datos ms compleja: la cola (que veremos ms adeltante).
No requieren memoria extra para soportar la expansin. Por el contrario, los arrays requieren memoria extra si se necesita
expandirlo (una vez que todos los elementos tienen datos no se pueden aadir datos nuevos a un array).
Ofrecen una insercin/borrado de elementos ms rpida que sus operaciones equivalentes en los arrays. Slo se tienen
que actualizar los enlaces despus de identificar la posicin de insercin/borrado. Desde la perspectiva de los arrays, la
insercin de datos requiere el movimiento de todos los otros datos del array para crear un elemento vaco. De forma
similar, el borrado de un dato existente requiere el movimiento de todos los otros datos para eliminar el elementovaco.
En contraste, los arrays ofrecen las siguientes ventajas sobre las listas enlazadas:
Los elementos de los arrays ocupan menos memoria que los nodos porque no requieren campos de enlace.
Los arrays ofrecen un acceso ms rpido a los datos, mediante ndices basados en enteros.
Las listas enlazadas son ms apropiadas cuando se trabaja con datos dinmicos. En otras palabras, inserciones y borrados con
frecuencia. Por el contrario, los arrays son ms apropiados cuando los datos son estticos (las inserciones y borrados son raras).
De todas formas, no olvide que si se queda sin espacio cuando aade tems a un array, debe crear un array ms grande, copiar los
datos del array original el nuevo array mayor y eliminar el original. Esto cuesta tiempo, lo que afecta especialmente al rendimiento si
se hace repetidamente.
Mezclando una lista de enlace simple con un array uni-dimensional para acceder a los nodos mediante los ndices del array no se
consigue nada. Gastar ms memoria, porque necesitar los elementos del array ms los nodos, y tiempo, porque necesitar
mover los tems del array siempre que inserte o borre un nodo. Sin embargo, si es posible integrar el array con una lista enlazada
para crear una estructura de datos til (por ejemplo, las tablas hash).
Pilas y Colas
Los desarrolladores utilizan los arrays y las variantes de listas enlazadas para construir una gran variedad de estructuras de datos
complejas. Este pgina explora dos de esas estructuras: las Pilas, las Colas . Cuando presentemos los algoritmos lo haremos
nicamente en cdigo Java por motivos de brevedad.
La Pila es una estructura 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 itm 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 programacin. Dos de los ms comunes son:
Es muy comn implementar una pila utilizando un array uni-dimensional o una lista de enlace simple. En el escenario del array unidimensional, una variable entera, tpicamente llamada top, contiene el ndice de la parte superior de la pila. De forma similar, una
variable de referencia, tambin nombrada normalmente como top, referencia el nodo superior del escenario de la lista de enlace
simple.
He modelado mis implementaciones de pilas despus de encontrar la arquitectura del API Collections de Java. Mis
implementaciones constan de un interface Stack para una mxima flexibilidad, las clases de implementacin ArrayStack y
LinkedListStack, y una clase de soporte FullStackException. Para facilitar su distribucin, 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);
// 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 combinacin de un ndice entero privado top y variables de referencia de un array unidimensional stack. top identifica el elemento superior de la pila y lo inicializa a -1 para indica que la pila est vaca. Cuando se
crea un objeto ArrayStack llama a public ArrayStack(int maxElements) con un valor entero que representa
el nmero mximo de elementos. Cualquier intento de sacar un elemento de una pila vaca mediante pop() resulta en el
lanzamiento de una java.util.EmptyStackException. De forma similar, cualquier intento de poner ms elementos
de maxElements dentro de la pila utilizando push(Object o) lanzar una FullStackException, cuyo cdigo
aparece en el siguiente listado:
// FullStackException.java
package com.javajeff.cds;
public class FullStackException extends RuntimeException {
}
Por simetra con EmptyStackException, FullStackException extiende RuntimeException. Como
resultado no se necesita aadir FullStackException a la clausula throws del mtodo.
El siguiente listado presenta una implementacin 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 combinacin 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 vaca. Al contrario que su contrapartida del array
uni-dimensional, LinkedListStack no necesita un constructor ya que se expande dinmicamente 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 vaca, lo que podra 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");
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 insercin de tem se hace en un final (el fin de la cola) y la recuperacin/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 trabajan con dos tipos de colas: lineal y circular. En ambas colas, la insercin de datos se realiza
en el fin de la cola, se mueven hacia adelante y se recuperan/borran del inicio 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 ms datos adicionales porque rear identifica la parte final de la cola. La razn de la posicin vaca, que identifica
front, implica el comportamiento lineal de la cola. Inicialmente, front y rear identifican la posicin ms a la izquierda, lo
que indica que la cola est vaca. Para almacenar el entero 1, rear avanza una posicin hacia la derecha y almacena 1 en esa
posicin. Para recuperar/borrar el entero 1, front avanza una posicin hacia la derecha.
Nota:
Para sealar que la cola lineal est vaca, no necesita gastar una posicin, aunque esta
aproximacin algunas veces es muy conneniente. En su lugar asigne el mismo valor que indique
una posicin no existente a front y a rear. Por ejemplo, asumiendo una implementacin basada
en un array uni-dimensional, front y rear podran contener -1. El ndice 0 indica entonces la
posicin ms a la izquierda, y los datos se insertarn empezando en este ndice.
Cuando rear identifique la posicin ms a la derecha, la cola lineal podra no estar llena porque
front podra haber avanzado almenos una posicin para recuperar/borrar un dato. En este
esceario, considere mover todos los tems de datos hacia la izquierda y ajuste la posicin de
front y rear de la forma apropiada para crear ms espacio. Sin embargo, demasiado movimiento
de datos puede afectar al rendimiento, por eso debe pensar cuidadosamente en los costes de
rendimiento si necesita crear ms 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
ms datos hasta que front avance una posicin en sentido horario (para recuperar el entero 1) y rear avance una posicin en
la misma direcin (para identificar la posicin que contendr el nuevo entero). Al igual que con la cola lineal, la razon de la posicin
vaca, que identifica front, implica el comportamiento circular de la cola. Inicialmente, front y rear identifican la misma
posicin, lo que indica una cola vaca. Entonces rear avanza una posicin por cada nueva insercin. De forma similar, front
avanza una posicin por cada recuperacin/borrado.
Las colas son muy tiles en varios escenarios de programacin, entre los que se encuentran:
Temporizacin de Threads:
Una JVM o un sistema operativo subyacente podran establecer varias colas para coincidir con diferentes prioridades de
los threads. La informacin del thread se bloquea porque todos los threads con una prioridad dada se almacenan en una
cola asociada.
Trabajos de impresin:
Como una impresora normalmente es ms lenta que un ordenador, un sistema operativo maneja los trabajos de impresin
en un subsistema de impresin, que inserta esos trabajos de impresin 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 coexistir mltiple 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 mtodos para almacenar un datos, determinar si la cola est vaca, determinar si la cola est llena y
recuperar/borrar un dato de la cola. Llame a estos mtodos (y a un constructor) para trabajar con cualquier implementacin de
Queue.
El siguiente listado presenta una a implementacin 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 combinacin de variables privadas front, rear, y queue. front y
rear se inicializan a -1 para indicar una cola vaca. Igual que el constructor de ArrayStack llama a public
ArrayLinearQueue(int maxElements) con un valor entero que especifique el nmero mximo de elementos
durante la construccin de un objeto ArrayLinearQueue.
El mtodo insert(Object o) de ArrayLinearQueue lanza una FullQueueException cuando rear
identifica el elemento final del array uni-dimensional. El cdigo de FullQueueException aparece en el siguiente listado:
// FullQueueException.java
package com.javajeff.cds;
public class FullQueueException extends RuntimeException {
}
El mtodo remove() de ArrayLinearQueue lanza una EmptyQueueException cuando los objetos front y
rear son iguales. El siguiente listado presenta el cdigo de esta clase:
// EmptyQueueException.java
package com.javajeff.cds;
public class EmptyQueueException extends RuntimeException {
}
El siguiente listado presenta una implementacin 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 () {
// 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
rboles
Organizacin Jerrquica con rboles
Un rbol es un grupo finito de nodos, donde uno de esos nodos sirve como raz y el resto de los nodos se organizan debajo de la
raz de una forma jerrquica. Un nodo que referencia un nodo debajo suyo es un nodo padre. De forma similar, un nodo
referenciado por un nodo encima de l, es un nodo hijo. Los nodos sin hijos, son nodos hoja. Un nodo podra ser un padre e hijo, o
un nodo hijo y un nodo hoja.
Un nodo padre podra referenciar tantos hijos como sea necesario. En muchas situaciones, los nodos padre slo referencian un
mximo de dos nodos hijos. Los rboles basados en dichos nodos son conocidos como rboles binarios. La siguiente figura
representa un rbol binario que almacena siete palabras en orden alfabtico.
Insertar nodos, borrar nodos, y atravesar los nodos en rboles binarios o de otros tipos se realiza mediante la recursin (vea el
captulo siguiente). Por brevedad, no entraremos en los algoritmos recursivos de insercin, borrados y movimiento por los nodos.
En su lugar, presentar el cdigo fuente de una aplicacin de conteo de palabras para demostrar la insercin y el movimiento por
los nodos. Este cdigo utiliza insercin de nodos para crear un rbol binario, donde cada nodo contiene una palabra y un contador
de ocurrencias de esa palabra, y muestra estas palabras y contadores en orden alfabtico mediante una variante del algoritmo de
movimiento por rboles move-left-examine-node-move-right:
// WC.java
import java.io.*;
class TreeNode {
String word;
// Word being stored.
int count = 1;
// Count of words seen in text.
TreeNode left;
// Left subtree reference.
TreeNode right;
// Right subtree reference.
public TreeNode (String word) {
this.word = word;
left = right = null;
}
public void insert (String word) {
int status
if (status
//
//
if
= this.word.compareTo (word);
> 0) {
// word argument precedes current word
If left-most leaf node reached, then insert new node as
its left-most leaf node. Otherwise, keep searching left.
(left == null)
left = new TreeNode (word);
else
left.insert (word);
}
else
if (status < 0) {
display (root);
}
static void display (TreeNode root) {
// If either the root node or the current node is null,
// signifying that a leaf node has been reached, return.
if (root == null)
return;
// Display all left-most nodes (i.e., nodes whose words
// precede words in the current node).
display (root.left);
// Display current node's word and count.
System.out.println ("Word = " + root.word + ", Count = " +
root.count);
// Display all right-most nodes (i.e., nodes whose words
// follow words in the current node).
display (root.right);
}
}
Como tiene muchos cometarios no explicar el cdigo. En su lugar le sugiero que juegue con esta aplicacin de esta forma: cuente
el nmero de palabras de un fichero, lance una lnea de comandos que incluya el smbolo de redireccin <. Por ejemplo, cuente el
nmero de palabras en WC.java lanzando java WC <WC.java. Abajo puede ver un extracto de la salida de este
comando:
Word
Word
Word
Word
Word
Word
Word
Word
Word
Word
Word
Word
Word
Word
=
=
=
=
=
=
=
=
=
=
=
=
=
=
Character, Count = 2
Count, Count = 2
Create, Count = 1
Display, Count = 3
IOException, Count = 1
If, Count = 4
Insert, Count = 1
Left, Count = 1
Otherwise, Count = 2
Place, Count = 2
Read, Count = 1
Right, Count = 1
String, Count = 4
StringBuffer, Count = 5
Recursin
La ciencia de la computacin hace tiempo que descubri que se puede reemplazar a un mtodo que utiliza un bucle para realizar
un clculo con un mtodo que se llame repetidamente a s mismo para realizar el mismo clculo. El echo de que un mtodo se
llame repetidamente a s mismo se conoce como recursion.
La recursin trabaja dividiendo un problema en subproblemas. Un programa llama a un mtodo con uno o ms parmetros que
describen un problema. Si el mtodo detecta que los valores no representan la forma ms simple del problema, se llama a s mismo
con valores de parmetros modificados que describen un subproblema cercano a esa forma. Esta actividad contina hasta que el
mtodo detecta la forma ms simple del problema, en cuyo caso el mtodo simplemente retorna, posiblemente con un valor, si el
tipo de retorno del mtodo no es void. La pila de llamadas a mtodo empieza a desbobinarse como una llamada a mtodo
anidada para ayudar a completar una evaluacin de expresin. En algn punto, la llamada el mtodo original se completa, y
posiblemente se devuelve un valor.
Para entender la recursin, consideremos un mtodo que suma todos los enteros desde 1 hasta algn lmite superior:
Cuidado:
Asegrese siempre que un mtodo recursivo tiene una condicin de parada (como if (limit ==
1) return 1;). Por el contrario, la recursin continuar hasta que se sobrecargue la pila de
llamadas a mtodos.