estructuras de datos y algoritmos en java - csudp - homede+estructura+de+datos.pdf · cuando...

49
Estructuras de Datos y Algoritmos en Java Autor: Jeff Friesen Traductor: Juan Antonio Palos (Ozito) Introducción Estructuras de Datos y Algoritmos Básicos o ¿Qué es una Estructura de Datos? o ¿Qué es un Algoritmo? o ¿Cómo se Representa un Algoritmo? § Flowchart § Pseudo código Arrays o Arrays de Una Dimensión § Utilizar sólo un Inicializador § Utilizar sólo la Palabra Clave "new" § Utilizar la palabra clave "new" y un Inicializador o Trabajar con un array uní-dimensional o Algoritmos de búsqueda-lineal, búsqueda-binaria y ordenación de burbuja § Búsqueda Lineal § Búsqueda Binaria § Ordenación de Burbuja o Arrays de Dos Dimensiones § Utilizar sólo un Inicializador § Utilizar sólo la palabra clave "new" § Utilizar la palabra clave "new" y un inicializador o trabajar con Arrays bi-dimensionales o Algoritmo de Multiplicación de Matrices o Arrays Desiguales o Los Arrays Java son Objetos Listas Enlazadas o Lista de Enlace Simple o Los Algoritmos de Concatenación e Inversión o Lista Doblemente Enlazada § Algoritmo de Inserción-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 Organización Jerárquica con Árboles o Recursión

Upload: dotu

Post on 01-Feb-2018

220 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Estructuras de Datos y Algoritmos en Java Autor: Jeff Friesen Traductor: Juan Antonio Palos (Ozito)

• Introducción • Estructuras de Datos y Algoritmos Básicos

o ¿Qué es una Estructura de Datos? o ¿Qué es un Algoritmo? o ¿Cómo se Representa un Algoritmo?

§ Flowchart § Pseudo código

• Arrays o Arrays de Una Dimensión

§ Utilizar sólo un Inicializador § Utilizar sólo la Palabra Clave "new" § Utilizar la palabra clave "new" y un Inicializador

o Trabajar con un array uní-dimensional o Algoritmos de búsqueda-lineal, búsqueda-binaria y ordenación de burbuja

§ Búsqueda Lineal § Búsqueda Binaria § Ordenación de Burbuja

o Arrays de Dos Dimensiones § Utilizar sólo un Inicializador § Utilizar sólo la palabra clave "new" § Utilizar la palabra clave "new" y un inicializador

o trabajar con Arrays bi-dimensionales o Algoritmo de Multiplicación de Matrices o Arrays Desiguales o Los Arrays Java son Objetos

• Listas Enlazadas o Lista de Enlace Simple o Los Algoritmos de Concatenación e Inversión o Lista Doblemente Enlazada

§ Algoritmo de Inserción-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 Organización Jerárquica con Árboles o Recursión

Page 2: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Introducción

La ciencia informática enfatiza dos tópicos importantes: las estructuras de datos y los algoritmos. Estos tópicos son importantes porque las elecciones que usted haga para las estructuras de datos y los algoritmos de un programa afectarán al uso de la memoria (las estructuras de datos) y al tiempo del procesador (los algoritmos que interactúan con esas estructuras de datos). Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa entre la utilización de memoria y el tiempo de CPU: cuanto menos memoria utiliza una estructura de datos, más 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 más memoria utilice una estructura de datos, menor tiempo de CPU necesitan los algoritmos asociados y el procesamiento de los ítems de datos es mucho más rápido. En la siguiente figura aparece está relación inversa.

Un ejemplo de la relación inversa entre la utilización 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 inserción/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 más 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 vacío para insertar o para borrar.

Estructuras de Datos y Algoritmos Básicos

Antes de explorar las estructuras de datos y sus algoritmos específicos, necesitamos examinar tres cuestiones básicas: ¿Qué es una estructura de datos? ¿Qué es un algoritmo? ¿Cómo se representa un algoritmo? El conocimiento de estos conceptos ayudará a entender este tutorial

¿Qué es una Estructura de Datos?

Las estructuras de datos nos han estado rodeando desde la era de la programación estructurada. Una definición de esa era: una estructura de datos es un conjunto de tipos, un tipo diseñado partiendo de ese conjunto de tipos, un conjunto de funciones, y un conjunto de axiomas. Esta definición implica que una estructura de datos es un tipo con implementación. En nuestra era de la programación orientadas a objetos, tipo con implementación significa clase.

La definición una estructura de datos es una clase es demasiado amplia porque supone que Empleado, Vehículo, Cuenta, y otras muchas clases específicas de entidades del mundo real son estructuras de datos. Aunque esas clases estructuran varios ítems de datos, describen entidades del mundo real (en la forma de objetos) en lugar de describir contenedores de objetos para otras entidades objetos (y posiblemente otro contenedor). Esta idea de contenido da una definición más apropiada para una estructura de datos: una estructura de datos es una clase contenedora que proporciona almacenamiento para ítems de datos, y capacidades para almacenar y recuperar estos datos. Algunos ejemplos de estructuras de datos son los arrays, las listas enlazadas, las pilas y las colas.

¿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 más entradas, produce al menos una salida, consiste en instrucciones claras y poco ambiguas, termina después de un número finito de pasos, y es lo suficientemente básico que una persona puede llevar a cabo el algoritmo utilizando lápiz y papel. Por el contrario, un programa no es necesariamente finito: el programa, como un servidor Web, podría no terminar nunca si no hay intervención externa. Algunos ejemplos de algoritmos

Page 3: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

asociados con estructuras de datos son: búsqueda-lineal, ordenación-de-burbuja, búsqueda-binaria, concatenación-de-listas-enlazadas, etc.

¿Cómo se Representa un Algoritmo?

La representación más obvia: código fuente Java. Sin embargo escribir código fuente antes de entender completamente un algoritmo normalmente acaba con bugs difíciles de encontrar. Una técnica para evitar estos bus es utilizar un flowchart (diagrama de flujo).

Flowchart

Un flowchart es una representación visual del flujo de control de un algoritmo. Esta representación ilustra las sentencias que se tienen que ejecutar, las decisiones que hay que tomar, el flujo lógico (para iteracciones y otros propósitos), y terminaciones que indican los puntos de entrada y salida. En la siguiente figura puede ver los distintos símbolos que puede utilizar en un flowchart:

¿Cuál 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 carácter de nueva línea (\n), incrementa el contador por cada carácter dígito leído, e imprime el valor del contador después de que haya leído el carácter de nueva línea. En la siguiente figura puede ver el flowchart que ilustra el flujo de control de este algoritmo:

Page 4: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Entre las ventajas de un flowchart se incluye su simplicidad y su habilidad para representar visualmente el flujo de control del algoritmo. Los flowcharts también tienen desventajas:

• Los flowcharts altamente detallados pueden generar errores o imprecisiones. • Se requiere algo de tiempo extra para posicionar, etiquetar y conectar los símbolos del flowchart, aunque algunas

herramientas aceleran este proceso, Este retardo podría ralentizar su entendimiento de un algoritmo. • Como los flowcharts son herramientas de la era de la programación estructurada, no son tan útiles en un contexto

orientado a objetos. Unified Modeling Language (UML) es más apropiado para proporcionar representaciones visuales orientadas a objetos.

Pseudocódigo

Una alternativa al flowchart es el pseudo código: una representación en modo texto de un algoritmo que se aproxima al código fuente final. El pseudo código es útil para una escritura rápida de representaciones de algoritmos. Como la sintaxis no es lo más importante, no hay reglas definidas para escribir pseudo código. Considere el siguiente ejemplo:

DECLARE CHARACTER ch DECLARE INTEGER count = 0 DO READ ch IF ch IS '0' THROUGH '9' THEN

Page 5: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

count++ END IF UNTIL ch IS '\n' PRINT count END

Este ejemplo representa el pseudo código equivalente al flowchart de la figura anterior. Aunque localizar el flujo de control en pseudocódigo puede costar un poco más que en un flowchart, normalmente, escribir pseudo código lleva menos tiempo que dibujar un flowchart.

Nota: En este tutorial se utiliza pseudocódigo para representar algoritmos.

Arrays

El array es una de las estructuras de datos más ampliamente utilizada por su flexibilidad para derivar en complejas estructuras de datos y su simplicidad. Empezaremos con una definición: 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 definición lanza cuatro puntos interesantes:

• Cada elemento ocupa el mismo número de bytes; el número 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 número de índices asociados con cada elemento es la dimensión del array

Nota: Esta sección 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 Dimensión

El tipo de array más simple tiene una dimensión: cada elemento se asocia con un único índice. Java proporciona tres técnicas para crear un array de una dimensión: usar sólo un inicializador, usar sólo la palabra clave new, y utilizar la palabra clave new con un inicializador.

Utilizar sólo un Inicializador

Utilizando un inicializador se puede utilizar cualquiera de estas dos sintaxis:

type variable_name '[' ']' [ '=' initializer ] ';' type '[' ']' variable_name [ '=' initializer ] ';'

Donde el inicializador tiene la siguiente sintaxis:

'{' initial_value1 ',' initial_value2 ',' ... '}'

El siguiente fragmento ilustra como crear un array de animales:

// Create an array of animals. String animals [] = { "Tiger", "Zebra", "Kangaroo" };

Page 6: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Utilizar sólo la Palabra Clave "new"

Utilizando la palabra clave new se puede utilizar cualquiera de estas dos sintaxis:

type variable_name '[' ']' '=' 'new' type '[' integer_expression ']' ';' type '[' ']' variable_name '=' 'new' type '[' integer_expression ']' ';'

Para ambas sintaxis:

• variable_name especifica el nombre de la variable del array uni-dimensional • type especifica el tipo de cada elemento. Como la variable del array uni-dimensional contiene una referencia a un array

uni-dimensional, el tipo es type [] • La palabra clave new seguida por type y seguida por integer_expression entre corchetes cuadrados ([])

específica el número de elementos. new asigna la memoria para los elementos del array uni-dimensional y pone ceros en todos los bits de los bytes de cada elementos, lo que significa que cada elemento contiene un valor por defecto que interpretamos basándonos en su tipo.

• = asigna la referencia al array uni-dimensional a la variable variable_name.

Truco: Los desarrolladores Java normalmente sitúan los corchetes cuadrados después del tipo (int [] test_scores) en vez de después del nombre de la variable (int test_scores []) cuando declaran una variable array. Mantener toda la información del tipo en un único lugar mejora la lectura del código.

El siguiente fragmento de código utiliza sólo la palabra clave new para crear un array uni-dimensional que almacena datos de un tipo primitivo:

int [] test_scores = new int [4];

int [] test_scores declara una variable array uni-dimensional (test_scores) junto con su tipo de variable (int []). El tipo de referencia int [] significa que cada elemento debe contener un ítem del tipo primitivo entero. new int [4] crea una array (arreglo) uni-dimensional asignando memoria para cuatro elementos enteros consecutivos. Cada elemento contiene un único entero y se inicializa a cero. El operador igual a (=) asigna la referencia del array uni-dimensional a test_scores. La siguiente figura ilustra los elementos y la variable array uni-dimensional resultante:

Page 7: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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 uni-dimensiones del tipo referencia almacenan datos que son referencias a objetos. El siguiente fragmento de código utiliza la palabra clave new para crear una pareja de arrays uni-dimensionales que almacenan datos basados en tipo referencia:

Clock [] c1 = new Clock [3]; Clock [] c2 = new AlarmClock [3];

Clock [] c1 = new Clock [3]; declara una variable array uni-dimensional, (c1) del tipo Clock [], asigna memoria para un array uni-dimensional Clock que consta de tres elementos consecutivos, y asigna la referencia del array Clock a c1. Cada elemento debe contener una referencia a un objeto Clock (asumiendo que Clock es una clase concreta) o un objeto creado desde una subclase de Clock y lo inicializa a null.

Clock [] c2 = new AlarmClock [3]; se asemeja a la declaración anterior, excepto en que se crea un array uni-dimensional AlarmClock, y su referencia se asgina a la variable Clock [] de nombre c2. (Asume AlarmClock como subclase de Clock.)

Utilizar la palabra clave "new" y un Inicializador

Utilizar la palabra clave new con un inicializador requiere la utilización de alguna de las siguientes síntaxis:

type variable_name> '[' ']' '=' 'new' type '[' ']' initializer ';' type '[' ']' variable_name '=' 'new' type '[' ']' initializer ';'

Donde initializer tiene la siguientes sintaxis:

'{' [ initial_value [ ',' ... ] ] '}'

• variable_name especifica el nombre de la variable del array uni-dimensional • type especifica el tipo de cada elemento. Como la variable del array uni-dimensional contiene una referencia a un array

uni-dimensional, el tipo es type [] • La palabra clave new seguida por type y seguida por corchetes cuadrados ([]) vacíos, seguido por inicializer.

No se necesita especificar el número de elementos entre los corchetes cuadrados porque el compilador cuenta el número 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 más dimensiones) creado con la palabra clave new con un inicializador algunas veces es conocido como un array anónimo.

El siguiente fragmento de código utiliza la palabra clave new con un inicializador para crear un array uni-dimensional con datos basados en tipos primitivos:

int [] test_scores = new int [] { 70, 80, 20, 30 };

Page 8: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

int [] test_scores declara una variable de array uni-dimensional (test_scores) junto con su tipo de variable (int []). El código 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 expresión 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 número de elementos partiendo del inicializador. Además, la discrepancia está entre el número 3 que hay en los corchetes y las cuatro entradas que hay en el inicializador.

La técnica de crear arrays uni-dimensionales con la palabra clave new y un inicializador también soporta la creación de arrays que contienen referencias a objetos. El siguiente fragmento de codigo utiliza esta técnica para crear una pareja de arrays uni-dimensionales que almacenan datos del tipo referencia:

Clock [] c1 = new Clock [] { new Clock () }; Clock [] c2 = new AlarmClock [] { new AlarmClock () };

Clock [] c1 = new Clock [3]; declara una variable de array uni-dimensional (c1) del tipo Clock [], asigna memoria para un array Clock que consta de un sólo elemento, crea un objeto Clock y asigna su referencia a este elemento, y asigna la referencia del array Clock a c1. El código Clock [] c2 = new AlarmClock [3]; se parece a la declaración anterior, excepto en que crea un array uni-dimensional de un sólo elemento AlarmClock que inicializa un objeto del tipo AlarmClock.

Trabajar con un array uni-dimensional

Después de crear un array uni-dimensional, hay que almacenar y recuperar datos de sus elementos. Con la siguiente sintaxis se realiza esta tarea:

variable_name '[' integer_expression ']'

integer_expression indentifica un índice de elemento y debe evaluarse como un entero entre 0 y uno menos que la longitud del array uni-dimensional (que devuelve variable_name.length). Un índice menor que 0 o mayor o igual que la longitud causa que se lance una ArrayIndexOutOfBoundsException. El siguiente fragmento de codigo ilustra accesos legales e ilegales a un elemento:

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]);

Page 9: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Ocurre una situación 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:

AlarmClock [] ac = new AlarmClock [1]; Clock [] c = ac; c [0] = new Clock ();

El compilador no dará ningún error porque todas las líneas son legales. Sin embargo, durante la ejecución, c [0] = new Clock (); resulta en una ArrayStoreException. Esta excepción ocurre porque podríamos intentar acceder a un miembro específico de AlarmClock mediante una referencia a un objeto Clock. Por ejemplo, supongamos que AlarmClock contiene un método public void soundAlarm(), Clock no lo tiene, y el fragmento de código anterior se ejecuta sin lanzar una ArrayStoreException. Un intento de ejecutar ac [0].soundAlarm (); bloquea la JVM Máquina Virtual Java porque estámos intentando ejecutar este método en el contexto de un objeto Clock (que no incorpora un método soundAlarm()).

Cuidado: Tenga cuidado cuando acceda a los elementos de un array porque podría recibir una ArrayIndexOutOfBoundsException o una ArrayStoreException.

Algoritmos de búsqueda-lineal, búsqueda-binaria y ordenación de burbuja

Los desarrolladores normalmente escribien código para buscar datos en un array y para ordenar ese array. Hay tres algoritmos muy comunes que se utilizan para realizar estas tareas.

Búsqueda Lineal

El algoritmo de búsqueda lineal busca en un array uni-dimensional un dato específico. La búsqueda primero examina el elemento con el índice 0 y continua examinando los elementos sucesivos hasta que se encuentra el ítem o no quedan más elementos que examinar. El siguiente pseudocódigo demuestra este algoritmo:

DECLARE INTEGER i, srch = 72 DECLARE INTEGER x [] = [ 20, 15, 12, 30, -5, 72, 456 ] FOR i = 0 TO LENGTH (x) - 1 IF x [i] IS srch THEN PRINT "Found ", srch END END IF NEXT i PRINT "Did not find ", srch END

El siguiente listado presenta el equivalente Java al pseudocódigo anterior:

// LSearchDemo.java class LSearchDemo { public static void main (String [] args) { int i, srch = 72; int [] x = { 20, 15, 12, 30, -5, 72, 456 }; for (i = 0; i <= x.length - 1; i++) if (x [i] == srch) {

Page 10: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

System.out.println ("Found " + srch); return; } System.out.println ("Did not find " + srch); } }

LSearchDemo produce la siguiente salida:

Found 72

Dos de las ventajas de la búsqueda lineal son la simplicidad y la habilidad de buscar tanto arrays ordenados como desornedados. Su única desventaja es el tiempo empleado en examinar los elementos. El número medio de elementos examinados es la mitad de la longitud del array, y el máximo número de elementos a examinar es la longitud completa. Por ejemplo, un array uni-dimensional con 20 millones de elementos requiere que una búsqueda lineal examine una media de 10 millones de elementos y un máximo de 20 millones. Como este tiempo de examen podría afectar seriamente al rendimiento, utilice la búsqueda lineal para arrays uni-dimensionales con relativamente pocos elementos.

Búsqueda Binaria

Al igual que en la búsqueda lineal, el algoritmo de búsqueda binaria busca un dato determinado en un array uni-dimensional. Sin embargo, al contrario que la búsqueda lineal, la búsqueda binaria divide el array en sección inferior y superior calculando el índice central del array. Si el dato se encuentra en ese elemento, la búsqueda binaria termina. Si el dato es numéricamente menor que el dato del elemento central, la búsqueda binaria calcula el índice central de la mitad inferior del array, ignorando la sección superior y repite el proceso. La búsqueda continua hasta que se encuentre el dato o se exceda el límite de la seccion (lo que indica que el dato no existe en el array). El siguiente pseudocódigo demuestra este algoritmo:

DECLARE INTEGER x [] = [ -5, 12, 15, 20, 30, 72, 456 ] DECLARE INTEGER loIndex = 0 DECLARE INTEGER hiIndex = LENGTH (x) - 1 DECLARE INTEGER midIndex, srch = 72 WHILE loIndex <= hiIndex midIndex = (loIndex + hiIndex) / 2 IF srch > x [midIndex] THEN loIndex = midIndex + 1 ELSE IF srch < x [midIndex] THEN hiIndex = midIndex - 1 ELSE EXIT WHILE END IF END WHILE IF loIndex > hiIndex THEN PRINT srch, " not found" ELSE PRINT srch, " found" END IF END

El siguiente código representa el equivalente Java del pseudocódigo anterior:

// BSearchDemo.java class BSearchDemo {

Page 11: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

public static void main (String [] args) { int [] x = { -5, 12, 15, 20, 30, 72, 456 }; int loIndex = 0; int hiIndex = x.length - 1; int midIndex, srch = 72; while (loIndex <= hiIndex) { midIndex = (loIndex + hiIndex) / 2; if (srch > x [midIndex]) loIndex = midIndex + 1; else if (srch < x [midIndex]) hiIndex = midIndex - 1; else break; } if (loIndex > hiIndex) System.out.println (srch + " not found"); else System.out.println (srch + " found"); } }

BSearchDemo produce la siguiente salida:

72 found

La única ventaja de la búsqueda binaria es que reduce el tiempo empleado en examinar elementos. El número máximo de elementos a examinar es log2n (donde n es la longitud del array uni-dimensional). Por ejemplo, un array uni-dimensional con 1.048.576 elementos requiere que la búsqueda binaria examine un máximo de 20 elementos. La búsqueda binaria tiene dos inconveniemtes; el incremento de complejidad y la necesidad de pre-ordenar el array.

Ordenación de Burbuja

Cuando entra en juego la ordenación de datos, la ordenación de burbuja es uno de los algoritmos más simples. Este algoritmo hace varios pases sobre un array uni-dimensional. Por cada pase, el algoritmo compara datos adyacentes para determinar si numéricamente 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 pseudocódigo dumuestra este algoritmo (en un contexto de ordenación ascendente):

DECLARE INTEGER i, pass DECLARE INTEGER x [] = [ 20, 15, 12, 30, -5, 72, 456 ] FOR pass = 0 TO LENGTH (x) - 2 FOR i = 0 TO LENGTH (x) - pass - 2 IF x [i] > x [i + 1] THEN SWAP x [i], x [i + 1] END IF NEXT i NEXT pass END

La siguiente figura muestra una ordenación de burbuja ascendente de un array uni-dimensional de cuatro elementos. Hay tres pasos, el paso 0 realiza tres comparaciones y dos intercambios, el paso 1 realiza dos comparaciones y un intercambio y el paso realiza una comparación y un intercambio.

Page 12: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

El siguiente listado presenta el equivalente Java del pseudocódigo anterior:

// 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 ordenación de burbuja es uno de los algoritmos de ordenación más simples, también es uno de los más lentos. Entre los algoritmos más rápidos se incluyen la ordenación rápida y la ordenación 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 código para realizar esta terea puede utilizar el método public static void arraycopy(Object src, int srcindex, Object dst, int dstindex, int length) de la clase java.lang.System, que es la forma más rápida de realizar la copia.

Page 13: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Arrays de Dos Dimensiones

Un array de dos dimensiones, también conocido como tabla o matriz, donde cada elemento se asocia con una pareja de índices, es otro array simple. Conceptualizamos un array bi-dimensional como una cuadrícula rectángular de elementos divididos en filas y columnas, y utilizamos la notación (fila, columna) para identificar un elemento específico. La siguiente figura ilustra esta visión conceptual y la notación específica de los elementos:

Java proporciona tres técnicas para crear un array bi-dimensional:

Utilizar sólo un Inicializador

Esta técnica requiere una de estas sintaxis:

type variable_name '[' ']' '[' ']' '=' '{' [ rowInitializer [ ',' ... ] ] '}' ';' type '[' ']' '[' ']' variable_name '=' '{' [ rowInitializer [ ',' ... ] ] '}' ';'

Donde rowInitializer tiene la siguiente sintaxis:

'{' [ initial_value [ ',' ... ] ] '}'

Para ambas sintaxis:

• variable_name especifica el nombre de la variable del array bi-dimensional. • type especifica el tipo de cada elemento. Como una variable de array bi-dimensional contiene una referencia a un array

bi-dimensional, su tipo es type [ ] [ ]. • Especifica cero o más inicializadores de filas entre los corchetes ({ }). Si no hay inicializadores de filas, el array bi-

dimensional está vacío. Cada inicializador de fila especifica cero o más valores iniciales para las entradas de las columnas de esa fila. Si no se especifican valores para esa fila, la fila está vacía.

• = se utiliza para asignar la referencia del array bi-dimensional a variable_name.

El siguiente código usa sólo un inicialiador para crear un array bi-dimensional que almacena datos basados en un tipo primitivo:

double [][] temperatures = { { 20.5, 30.6, 28.3 }, { -38.7, -18.3, -16.2 } }; // Celsius temperatures

double [][] temperatures declara una variable de array bi-dimensional (temperatures) junto con su tipo de variable (double [][]). El tipo de referencia double [][] signigica que cada elemento debe contener datos del tipo primitivo double. { { 20.5, 30.6, 28.3 }, { -38.7, -18.3, -16.2 } } especifica un array

Page 14: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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. Detrás 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.

Utilizar sólo la palabra clave "new"

Esta técnica requiere cualquiera de estas sintaxis:

type variable_name '[' ']' '[' ']' '=' 'new' type '[' integer_expression ']' '[' ']' ';' type '[' ']' '[' ']' variable_name '=' 'new' type '[' integer_expression ']' '[' ']' ';'

En ambas sintaxis:

• variable_name especifica el nombre de la variable del array bi-dimensional.

Page 15: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

• 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 expresión entera entre corchetes cuadrados, que indentifica el número 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 código usa sólo la palabra clave new para crear un array bi-dimensional que almacena datos basados en un tipo primitivo:

double [][] temperatures = new double [2][]; // Allocate two rows. temperatures [0] = new double [3]; // Allocate three columns for row 0 temperatures [1] = new double [3]; // Alllocate three columns for row 1 temperatures [0][0] = 20.5; // Populate row 0 temperatures [0][1] = 30.6; temperatures [0][2] = 28.3; temperatures [1][0] = -38.7; // Populate row 1 temperatures [1][1] = -18.3; temperatures [1][2] = -16.2;

Utilizar la palabra clave "new" y un inicializador

Esta técnica requiere una de estas sintaxis:

type variable_name '[' ']' '[' ']' '=' 'new' type '[' ']' '[' ']' '{' [ rowInitializer [ ',' ... ] ] '}' ';' type '[' ']' '[' ']' variable_name '=' 'new' type '[' ']' '[' ']' '{' [ rowInitializer [ ',' ... ] ] '}' ';'

donde rowInitializer tiene la siguiente sintaxis:

'{' [ initial_value [ ',' ... ] ] '}'

En ambas sintaxis:

• variable_name especifica el nombre de la variable del array bi-dimensional. • 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 dos parejas de corchetes cuadrados vacíos, y cero o más inicializadores

de filas enrre un par de corchetes cuadrados. Si no se especifica ningun inicializador de fila, el array bi-dimensional está vacío. Cada inicializador de fila especifica cero o más 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 código usa la palabra clave new y un inicilizador para crear un array bi-dimensional que almacena datos basados en un tipo primitivo:

double [][] temperatures = new double [][] { { 20.5, 30.6, 28.3 }, { -38.7, -18.3, -16.2 } };

El fragmento de código de arriba se comporta igual que el anterior double [][] temperatures = new double [][] { { 20.5, 30.6, 28.3 }, { -38.7, -18.3, -16.2 } };.

Page 16: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

trabajar con Arrays bi-dimensionales

Después de crear un array bi-dimensional, querrá almacenar y recuperar datos de sus elementos. Puede realizar estas tareas con la siguiente sintaxis:

variable_name '[' integer_expression1 ']' '[' integer_expression2 ']'

integer_expression1 identifica el índice de fila del elemento y va de cero hasta la longitud del array menos uno. Igual ocurre con integer_expression2 pero para el índice de columna. como todas las filas tienen la misma longitud, podría encontrar conveniente especificar variable_name [0].length para especificar el número de columnas de cualquier fila. El siguiente fragmento de código almacena y recupera datos de un elemento de un array bi-dimensional:

double [][] temperatures = { { 20.5, 30.6, 28.3 }, { -38.7, -18.3, -16.2 } }; temperatures [0][1] = 18.3; // Replace 30.6 with 18.3 System.out.println (temperatures [1][2]); // Output: -16.2

Algoritmo de Multiplicación de Matrices

Multiplicar una matriz por otra es una operación común en el trabajo con gráficos, con datos económicos, o con datos industriales. Los desarrolladores normalmente utilizan el algoritmo de multiplicación de matrices para completar esa multiplicación. ¿Cómo funciona ese algoritmo? Dejemos que 'A' represente una matriz con 'm' filas y 'n' columnas. De forma similar, 'B' representa un matriz con 'p' filas y 'n' columnas. Multiplicar A por B produce una matriz C obtenida de multiplicar todas las entradas de A por su correpondencia en B. La siguiente figura ilustra estas operaciones.

Cuidado:

La multiplicación de matrices requiere que el número 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 pseudocódigo demuestra el algoritmo de multiplicación de matrices:

// __________ _____ _________________________ // | 10 30 | | 5 | | 10 x 5 + 30 x 7 (260) | // | | X | | = | | // | 20 40 | | 7 | | 20 x 5 + 40 * 7 (380) | // __________ _____ _________________________ DECLARE INTEGER a [][] = [ 10, 30 ] [ 20, 40 ]

Page 17: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

DECLARE INTEGER b [][] = [ 5, 7 ] DECLARE INTEGER m = 2 // Number of rows in left matrix (a) DECLARE INTEGER p = 2 // Number of columns in left matrix (a) // Number of rows in right matrix (b) DECLARE INTEGER n = 1 // Number of columns in right matrix (b) // Note: cs1 must equal rs2 DECLARE INTEGER c [m][n] // c holds 2 rows by 1 columns // All elements initialize to 0 FOR i = 0 TO m - 1 FOR j = 0 TO n - 1 FOR k = 0 TO p - 1 c [i][j] = c [i][j] + a [i][k] * b [k][j] NEXT k NEXT j NEXT i

El pseudocódigo de arriba requiere tres bucles FOR para realizar la multiplicación. El bucle más interno multiplica una sóla fila de la matriz a por un sola columna de la matriz B y añade el resultado a un sola entrada de la matriz C. El siguiente listado presenta el equivalente Java del pseudocódigo 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 //

Page 18: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

// 4. b [0].length (or any other b [x].length for a valid x) // contains b's column count // ========================================================= // If a's column count != b's row count, bail out if (a [0].length != b.length) { System.err.println ("a's column count != b's row count"); return null; } // Allocate result matrix with a size equal to a's row count x b's // column count int [][] result = new int [a.length][]; for (int i = 0; i < result.length; i++) result [i] = new int [b [0].length]; // Perform the multiplication and addition for (int i = 0; i < a.length; i++) for (int j = 0; j < b [0].length; j++) for(int k = 0; k < a [0].length; k++) // Or k < b.length result [i][j] += a [i][k] * b [k][j]; // Return the result matrix return result; } }

MatMultDemo produce esta salida:

10 30 20 40 5 7 260 380

Exploremos un problema donde se necesita la multiplicación de matrices para obtener una solución. Un frutero de Florida carga una pareja de semitrailers con 1250 cajas de naranjas, 400 cajas de melocotones y 250 cajas de uvas. En la siguiente figura aparece la tabla de precios de mercado, por cada tipo de fruta en cinco ciudades diferentes.

Page 19: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

¿A qué ciudades debería enviar los semitrailers para obtener el máximo 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 8.00 12.00 | == == | 18700.00 | New York | | | 1250 | | | | 11.00 8.50 11.55 | | | | 20037.50 | Los Angeles | |X | 400 | = | | | 8.75 6.90 10.00 | | | | 16197.50 | Miami | | | 250 | | | | 10.50 8.25 11.75 | == == | 19362.50 | Chicago == == == ==

Enviar los dos semitrailers a Los Angles produce el mayor ingreso. Pero cuando se consideren la distancia y el consumo de gasoil, quizás New York sea la mejor apuesta

Arrays Desiguales

Suponga que su código fuente contiene la siguiente declaración de matriz: int [][] x = new int [5][];. Esto declara una matriz de enteros que contiene cinco filas, y x.length devuelve ese número de filas. Normalmente, completa la creación de la matriz especificando el mismo número de columnas para cada fila. Por ejemplo, especificando 10 columnas para cada fila utilizando el siguiente código:

for (int i = 0; i < x.length; i++) x [i] = new int [10];

Al contrario que otros lenguajes, Java no le fuerza a especificar el mismo número de columnas por cada fila. Utilizando el fragmento de codigo anterior, asigne tres columnas a la fila cero, dos a la fila 1, tres a la fila 2, cinco a la fila 3 y una a la fila 4, lo que demuestra el siguiente fragmento de código:

x [0] = new int [3]; x [1] = new int [2]; x [2] = new int [3]; x [3] = new int [5]; x [4] = new int [1];

Después de ejecutar este código, usted tendrá una matriz degenerada conocida como un array desigual. La siguiente imagen ilustra este tipo de arrays:

Page 20: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Los arrays desiguales son estructuras de datos útiles debido a su capacidad de ahorro de memoria. Por ejemplo, considere una hoja de cálculo 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 mayoría de la celdas contienen valores por defecto, como un 0 para valores numéricos y null para celdas no numéricas. Si utilizamos un array desigual en lugar de una matriz, almacenaremos sólo las celdas que contienen datos numéricos. (Por supuesto, necesitamos algún tipo de mecanismo de mapeo que mapee las coordenadas de la hoja de cálculo [filas, columnas] a las coordenadas del array desigual [filas], [columnas]).

Los Arrays Java son Objetos

La primera sentencia del capítulo 10 de la Especificación del Lenguaje Java dice lo siguiente: En el lenguaje Java, los arrays son objetos. Detrás de la escena, cada array es un ejemplar de una clase oculta que hereda 11 métodos de la clase Object y sobrescribe el método protected Object clone() throws CloneNotSupportedException de la misma clase para que un array pueda ser clonado en la sombra. Además, esta clase oculta proporciona un campo length. El siguiente listado demuestra la asociación entre arrays y objetos:

// 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]); } }

Page 21: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Cuando se ejecuta ArrayIsObject produce la siguiente salida:

a's class is class [D a and b are not equal b and c are equal c and d are not equal 100.5 200.5 300.5

ArrayIsObject crea las referencias a los arrays a- y b con la misma precisión y los mismos contenidos y las misma longitud. Para el array a, a.getClass () devuelve class [D, donde [D es el nombre de la clase oculta. A pesar de que ambos arrays tienen los mismos contenidos, a.equals (b) devuelve false porque equals() compara referencias (no contenidos), y a y b contienen diferentes referencias. La referencia de b se asigna a c, y b.equals (c) devuelve true porque b y c referencian al mismo array. c.clone() crea un clon de c, y una referencia de ese array se asigna a d. Para probar que la referencia d contiene los mismos contenidos que la referencia del array c, el bucle for itera sobre todos los elementos e imprime su contenido. El bucle lee los contenidos y el campo de sólo lectura length para determinar sobre cuantos elementos iterar.

Truco: En el código 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 después decide modificar la longitud del array en su código de creación.

Listas Enlazadas

Además 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.

• Nodo: un objeto creado desde una clase auto-referenciada. • Campo de enlace: un campo cuyo tipo de referencia es el nombre de la clase. En el fragmento de código anterior, next

es un campo de enlace. Por el contrario, empno, name, y salary son campos no de enlace. • Enlace: la referencia a un campo de enlace. En el fragmento de código anterior, la referencia next a un nodo

Employee es un enlace.

Los cuatro conceptos de arriba nos llevan a la siguiente definición: una lista enlazada es una secuencia de nodos que se interconectan mediante sus campos de enlace. En ciencia de la computación se utiliza una notación especial para ilustrar las listas enlazadas. En la siguiente imagen aparece una variante de esta notación que utilizaré a lo largo de esta sección:

Page 22: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

La figura anterior presenta tres nodos: A, B y C. Cada nodo se divide en áreas de contenido (en naranja) y una o más á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 ningún otro nodo.

Aunque se pueden crear muchos tipos de listas enlazadas, las tres variantes más populares son la lista de enlace simple, la lista doblemente enlazada y la lista enlazada circular. Exploremos esas variantes, empezando con la lista enlazada.

Lista de Enlace Simple

Una lista de enlace simple es una lista enlazada de nodos, donde cada nodo tiene un único campo de enlace. Una variable de referencia contiene una referencia al primer nodo, cada nodo (excepto el último) enlaza con el nodo siguiente, y el enlace del último nodo contiene null para indicar el final de la lista. Aunque normalmente a la variable de referencia se la suele llamar top, usted puede elegir el nombre que quiera. La siguiente figura presenta una lista de enlace simple de tres nodos, donde top referencia al nodo A, A conecta con B y B conecta con C y C es el nodo final:

Un algoritmo común de las listas de enlace simple es la inserción 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 después 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 pseudocódigo:

DECLARE CLASS Node DECLARE STRING name DECLARE Node next END DECLARE DECLARE Node top = NULL

Este pseudocódigo declara una clase auto-referenciada llamada Node con un campo no de enlace llamado name y un campo de enlace llamado next. También declara una variable de referencia top (del tipo Node) que contiene una referencia al primer Node de una lista de enlace simple. Como la lista todavía no existe, el valor inicial de top es NULL. Cada uno de los siguientes cuatro casos asume las declaraciones de Node y top:

• La lista de enlace simple no existe:: Este es el caso más simple. Se crea un Node, se asigna su referencia a top, se inicializa su campo no de enlace, y se asigna NULL a su campo de enlace. El siguiente pseudocódigo realiza estas tareas:

• top = NEW Node

Page 23: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

• • top.name = "A" • top.next = NULL

En la siguiente imagen se puede ver la lista de enlace simple que emerge del pseudocódigo anterior:

• El nodo debe insertarse antes del primer nodo:. Se crea un Node, se inicialia su campo no de enlace, se asigna la referencia de top al campo de enlace next, y se asigna la referencia del Node recien creado a top. El siguiente pseudocódigo (que asume que se ha ejecutado el pseudocódigo anterior) realiza estas tareas:

• DECLARE Node temp • • temp = NEW Node • temp.name = "B" • temp.next = top • top = temp

El resultado del listado anterior aparece en la siguiente imagen:

• El nodo debe insertarse detrás del último nodo: Se crea un Node, se inicializa su campo no de enlace, se asigna NULL al campo de enlace, se atraviesa la lista de enlace simple hasta el último Node, y se asigna la referencia del Node recien creado al campo next del último nodo. El siguiente pseudocódigo realiza estas tareas:

• temp = NEW Node • temp.name = "C" • temp.next = NULL • • DECLARE Node temp2 • • temp2 = top • • // We assume top (and temp2) are not NULL • // because of the previous pseudocode • • WHILE temp2.next IS NOT NULL • temp2 = temp2.next • END WHILE • • // temp2 now references the last node • • temp2.next = temp

La siguiente imagen revela la lista después de la inserceción del nodo C después del nodo A.

Page 24: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

• El nodo se debe insertar entre dos nodos: Este es el caso más complejo. Se crea un Node, se inicializa su campo no de enlace, se atraviesa la lista hasta encontrar el Node que aparece antes del nuevo Node, se asigna el campo de enlace del Node anterior al campo de enlace del Node recien creado, y se asigna la referencia del Node recien creado al campo del enlace del Node anterior. El siguiente pseudocódigo realiza estas tareas:

• temp = NEW Node • temp.name = "D" • • temp2 = top • • // We assume that the newly created Node is inserted after Node • // A and that Node A exists. In the real world, there is no • // guarantee that any Node exists, so we would need to check • // for temp2 containing NULL in both the WHILE loop's header • // and after the WHILE loop completes. • • WHILE temp2.name IS NOT "A" • temp2 = temp2.next • END WHILE • • // temp2 now references Node A. • • temp.next = temp2.next • temp2.next = temp

La siguiente imagen muestra la inserción del nodo D entre los nodos A y C.

El siguiente listado presenta el equivalente Java de los ejemplos de pseudo código de inserción 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

Page 25: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

// 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 método static void dump(String msg, Node topNode) itera sobre la lista e imprime su contenido. Cuando se ejecuta SLLInsDemo, las repetidas llamadas a este método dan como resultado la siguiente salida, lo que coincide con las imagénes anteriores:

Case 1 A Case 2 B A Case 3 B A C Case 4 B A D C

Nota: SLLInsDemo y los ejemplos de pseudocódigo anteriores empleaban un algoritmo de búsqueda lineal orientado a listas enlazadas para encontrar un Node específico. Indudablemente usted utilizará este otro algoritmo en sus propios programas:

• Búsqueda del últimoNode: • // Assume top references a singly linked list of at least one Node.

Page 26: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

• • 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. • Búsqueda de un Node específico: • // 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 común de las listas de enlace simples es el borrado de nodos. Al contrario que la insercción de nodos, sólo hay dos casos a considerar:

• Borrar el Primer nodo: Asigna el enlace del campo next del nodo referenciado por top a top:

• 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.

• Borrar cualquier nodo que no sea el primero: Localiza el nodo que precede al nodo a borrar y le asigna el enlace que hay en el campo next del nodo a borrar al campo next del nodo que le precede. El siguiente pseudocódigo borra el nodo D:

• temp = top • WHILE temp.name IS NOT "A" • temp = temp.next

Page 27: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

• 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.

El siguiente listado representa el equivalente Java a los pseudocódigos de borrado anteriores:

// 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;

Page 28: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

temp = new Node (); temp.name = "B"; temp.next = top; top = temp; dump ("Initial singly-linked list", top); // 1. Delete the first node top = top.next; dump ("After first node deletion", top); // Put back B temp = new Node (); temp.name = "B"; temp.next = top; top = temp; // 2. Delete any node but the first node temp = top; while (temp.name.equals ("A") == false) temp = temp.next; temp.next = temp.next.next; dump ("After D node deletion", 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 (); } }

Cuando ejecute SLLDelDemo, observará la siguiente salida:

Initial singly linked list B A D C After first node deletion A D C After D node deletion B A C

Cuidado: Como java inicializa los campos de referencias de un objeto a null durante la construcción del objeto, no es necesario asignar explícitamente null a un campo de enlace. No olvide estas asignaciones de null en su código fuente; su ausencia reduce la claridad del código.

Después de estudiar SLLDelDemo, podría preguntarse qué sucede si asigna null al nodo referenciado por top: ¿el recolector de basura recogerá toda la lista? Para responder a esta cuestión, compile y ejecute el código 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 (); } }

Page 29: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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; temp = new Node (); temp.name = "B"; temp.next = top; top = temp; dump ("Initial singly-linked list", top); top = null; temp = null; for (int i = 0; i < 100; i++) System.gc (); } 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 (); } }

GCDemo crea la misma lista de cuatro nodos que SLLDelDemo. Después de volcar los nodos a la salida estándar, GCDemo asigna null a top y a temp. Luego, GCDemo ejecuta System.gc (); hasta 100 veces. ¿Qué sucede después? Mire la salida (que he observado en mi plataforma Windows):

Initial singly-linked list B A D C Finalizing C Finalizing D Finalizing A Finalizing B

La salida revela que todos los nodos de la lista de enlace simple han sido finalizados (y recolectados). Como resultado, no tiene que preocuparse de poner a null todos los enlaces de una lista de enlace simple cuando se quiera deshacer de ella. (Podría necesitar tener que incrementar el número de ejecuciones de System.gc (); si su salida no incluye los mensajes de finalización.)

Los Algoritmos de Concatenación e Inversión

Existen muchos algoritmos útiles para listas de enlace simple. Uno de ellos es la concatenación, que implica que puede añadir una lista de enlace simple al final de otra lista.

Otro algoritmo útil es la inversión. Este algoritmo invierte los enlaces de una lista de enlace simple permitiendo atravesar los nodos en direccion opuesta. El siguiente código extiende la clase anterior para invertir los enlaces de la lista referenciada por top1:

Page 30: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

> // 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; }

Page 31: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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 más alto nivel cuyos objetos contienen palabras y significados. (Para mentener el programa lo más sencillo posible, he evitado los significados. Usted puede añadirlos si lo desea). CIDemo también declara ListInfo para seguir las referencias el primero y último DictEntry de una lista de enlace simple.

El thread principal ejecuta el método public static void main(String [] args) de CIDemo. Este thread llama dos veces al método 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 método 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 método devuelve directamente un sólo dato). Después 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 estándar.

Se podría estar preguntando sobre la necesidad del campo last de ListInfo. Este campo sirve a un doble propósito: primero, simplifica la creación de cada lista, donde se añaden los nodos. Segundo, este campo simplifica la concatenación, que se queda sólo en la ejecución de la siguiente línea de código: liMaster.last.next = liWorking.top;. Una vez que se completa la concatenación, y el thread principal vuelva los resultados de la lista maestra en la salida estándar, el thread llama al método static void invert (ListInfo li) para invertir la lista maestra y luego muestra la lista maestra invertida por la salida estándar.

Cuando ejecute CIDemo verá la siguiente salida:

Master list = aardvark anxious asterism Working list = carbuncle catfish color New master list = aardvark anxious asterism carbuncle catfish color Inverted new master list = color catfish carbuncle asterism anxious aardvark

Lista Doblemente Enlazada

Las listas de enlace simple restringen el movimiento por lo nodos a una sóla dirección: no puede atravesar una lista de enlace simple en dirección opuesta a menos que primero utilice el algoritmo de inversión para invertir los enlaces de los nodos, lo que lleva tiempo. Después de atraversarlos en dirección opuesta, problamente necesitará repetir la inversión para restaurar el orden original, lo que lleva aún más tiempo. Un segundo problema implica el borrado de nodos: no puede borrar un nodo arbitrario sin acceder al predecesor del nodo. Estos problemas desaperecen cuando se utiliza una lista doblemente enlazada.

Una lista doblemente enlazada es una lista enlazada de nodos, donde cada nodo tiene un par de campos de enlace. Un campo de enlace permite atravesar la lista hacia adelante, mientras que el otro permite atravesar la lista haca atrás. Para la dirección hacia adelante, una variable de referencia contiene una referencia al primer nodo. Cada nodo se enlaza con el siguiente mediante el campo de enlace next, excepto el último nodo, cuyo campo de enlace next contiene null para indicar el final de la lista (en direccion hacia adelante). De forma similar, para la dirección contraria, una variable de referencia contiene una referencia al último nodo de la dirección normal (hacia adelante), lo que se interpreta como el primer nodo. Cada nodo se enlaza con el anterior mediante el campo de enlace previous, y el primer nodo de la direccion hacia adelante, contiene null en su campo previous para indicar el fin de la lista. La siguiente figura representa una lista doblemente enlazada de tres nodos, donde

Page 32: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

topForward referencia el primer nodo en la direccion hacia adelante, y topBackward referencia el primero nodo la dirección inversa.

Truco:

Piense en una lista doblemente enlazada como una pareja de listas de enlace simple que interconectan los mismos nodos.

La inserción 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 inserción y borrado de las listas de enlace simple (porque las listas doblemente enlazadas sólo son una pareja de listas de enlace simple que interconectan los mismos nodos).

El siguiente listado muestra la inserción 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";

Page 33: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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:

Forward singly-linked list: ABC Backward singly-linked list: CBA Forward singly-linked list (after deletion): AC Backward singly-linked list (after deletion): CA

Page 34: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Algoritmo de Inserción-Ordenada

Algunas veces querrá crear una lista doblemente enlazada que organice el orden de sus nodos basándose en un campo no de enlace. Atravesar la lista doblemente enlazada en una dirección presenta esos nodos en orden ascendente, y atravesarla en en dirección contraria los presenta ordenados descedentemente. El algoritmo de ordenación de burbuja es inapropiado en este caso porque requiere índices de array. Por el contrario, inserción-ordenada construye una lista de enlace simple o una lista doblemente enlzada ordenadas por un campo no de enlace para identificar el punto de inserción de cada nuevo nodo. El siguiente litado demuestra el algoritmo de inserción-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. }

Page 35: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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 operación creando primero un nodo Employee primario. Para el resto de nodos Employee, InsSortDemo localiza la posición de inserción apropiada basándose en el campo no de enlace empno, y luego inserta el Employee en esa posición. 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]

Page 36: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

[100, George]

Tanto la inserción-ordenada como la ordenación de burbuja exhiben prácticamente el mismo rendimiento.

Lista de Enlace Circular

El campo de enlace del último nodo de una lista de enlace simple contiene un enlace nulo, ocurre lo mismo en los campos de enlace del primer y último elemento en ambas direcciones en las listas doblemente enlazadas. Supongamos que en vez de esto los últimos nodos contiene un enlace a los primeros nodos. En esta situacion, usted terminará con una lista de enlace circular, como se ve en la siguiente figura:

Las listas de enlace circular se utilizan con frecuencia en procesamiento repetitivo de nodos en un orden específico. Dichos nodos podrían representar conexiones de servidor, procesadores esperando una sección crítica, etc. Esta estructura de datos también sirve como base para una variante de una estructura de datos más compleja: la cola (que veremos más adeltante).

Listas Enlazadas frente a Arrays

Las listas enlazadas tienen las siguientes ventajas sobre los arrays:

• No requieren memoria extra para soportar la expansión. Por el contrario, los arrays requieren memoria extra si se necesita expandirlo (una vez que todos los elementos tienen datos no se pueden añadir datos nuevos a un array).

• Ofrecen una inserción/borrado de elementos más rápida que sus operaciones equivalentes en los arrays. Sólo se tienen que actualizar los enlaces después de identificar la posición de inserción/borrado. Desde la perspectiva de los arrays, la inserción de datos requiere el movimiento de todos los otros datos del array para crear un elemento vacío. De forma similar, el borrado de un dato existente requiere el movimiento de todos los otros datos para eliminar el elementovacío.

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 más rápido a los datos, mediante índices basados en enteros.

Las listas enlazadas son más apropiadas cuando se trabaja con datos dinámicos. En otras palabras, inserciones y borrados con frecuencia. Por el contrario, los arrays son más apropiados cuando los datos son estáticos (las inserciones y borrados son raras). De todas formas, no olvide que si se queda sin espacio cuando añade ítems a un array, debe crear un array más 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á más memoria, porque necesitará los elementos del array más 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 página explora dos de esas estructuras: las Pilas, las Colas . Cuando presentemos los algoritmos lo haremos únicamente en código Java por motivos de brevedad.

Pilas que "Recuerdan"

Page 37: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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 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 normalmente 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);

Page 38: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

Object pop (); }

Sus cuatro métodos determinan si la pila está vacía, recuperan el elemento superior sin borrarlo de la pila, sitúan un elemento en la parte superior de la pila y el último recupera/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:

Page 39: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

// 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");

Page 40: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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

Page 41: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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 trabajan 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 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 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

Page 42: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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;

Page 43: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

} 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 () {

Page 44: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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");

Page 45: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

} 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

Page 46: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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

Organización Jerárquica con Árboles

Un árbol es un grupo finito de nodos, donde uno de esos nodos sirve como raíz y el resto de los nodos se organizan debajo de la raíz de una forma jerárquica. 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 podría ser un padre e hijo, o un nodo hijo y un nodo hoja.

Un nodo padre podría referenciar tantos hijos como sea necesario. En muchas situaciones, los nodos padre sólo referencian un máximo 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 alfabético.

Insertar nodos, borrar nodos, y atravesar los nodos en árboles binarios o de otros tipos se realiza mediante la recursión (vea el capítulo siguiente). Por brevedad, no entraremos en los algoritmos recursivos de inserción, borrados y movimiento por los nodos. En su lugar, presentaré el código fuente de una aplicación de conteo de palabras para demostrar la inserción y el movimiento por los nodos. Este código utiliza inserción 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 alfabético 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) {

Page 47: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

int status = this.word.compareTo (word); if (status > 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. if (left == null) left = new TreeNode (word); else left.insert (word); } else if (status < 0) { // word argument follows current word // If right-most leaf node reached, then insert new node as // its right-most leaf node. Otherwise, keep searching right. if (right == null) right = new TreeNode (word); else right.insert (word); } else this.count++; } } class WC { public static void main (String [] args) throws IOException { int ch; TreeNode root = null; // Read each character from standard input until a letter // is read. This letter indicates the start of a word. while ((ch = System.in.read ()) != -1) { // If character is a letter then start of word detected. if (Character.isLetter ((char) ch)) { // Create StringBuffer object to hold word letters. StringBuffer sb = new StringBuffer (); // Place first letter character into StringBuffer object. sb.append ((char) ch); // Place all subsequent letter characters into StringBuffer // object. do { ch = System.in.read (); if(Character.isLetter ((char) ch)) sb.append((char) ch); else break; } while (true); // Insert word into tree. if (root == null) root = new TreeNode (sb.toString ()); else root.insert (sb.toString ()); } }

Page 48: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

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 código. En su lugar le sugiero que juegue con esta aplicación de esta forma: cuente el número de palabras de un fichero, lance una línea de comandos que incluya el símbolo de redirección <. Por ejemplo, cuente el número de palabras en WC.java lanzando java WC <WC.java. Abajo puede ver un extracto de la salida de este comando:

Word = Character, Count = 2 Word = Count, Count = 2 Word = Create, Count = 1 Word = Display, Count = 3 Word = IOException, Count = 1 Word = If, Count = 4 Word = Insert, Count = 1 Word = Left, Count = 1 Word = Otherwise, Count = 2 Word = Place, Count = 2 Word = Read, Count = 1 Word = Right, Count = 1 Word = String, Count = 4 Word = StringBuffer, Count = 5

Recursión

La ciencia de la computación hace tiempo que descubrió que se puede reemplazar a un método que utiliza un bucle para realizar un cálculo con un método que se llame repetidamente a sí mismo para realizar el mismo cálculo. El echo de que un método se llame repetidamente a sí mismo se conoce como recursion.

La recursión trabaja dividiendo un problema en subproblemas. Un programa llama a un método con uno o más parámetros que describen un problema. Si el método detecta que los valores no representan la forma más simple del problema, se llama a sí mismo con valores de parámetros modificados que describen un subproblema cercano a esa forma. Esta actividad continúa hasta que el método detecta la forma más simple del problema, en cuyo caso el método simplemente retorna, posiblemente con un valor, si el tipo de retorno del método no es void. La pila de llamadas a método empieza a desbobinarse como una llamada a método anidada para ayudar a completar una evaluación de expresión. En algún punto, la llamada el método original se completa, y posiblemente se devuelve un valor.

Para entender la recursión, consideremos un método que suma todos los enteros desde 1 hasta algún límite superior:

static int sum (int limit) { int total = 0;

Page 49: Estructuras de Datos y Algoritmos en Java - csudp - homede+Estructura+de+Datos.pdf · Cuando utiliza una estructura de datos o un algoritmo alguna veces descubre una relación inversa

for (int i = 1; i <= limit; i++) total += i; return total; }

Este método es correcto porque consigue el objetivo. Después de crear una variable local total e inicializarla a cero, el método usa un bucle for para sumar repetidamente enteros a total desde 1 hasta el valor del parámetro limit. Cuando la suma se completa, sum(int limit) devuelve el total, mediante return total;, a su llamador.

La recursión hace posible realizar está suma haciendo que sum(int limit) se llame repetidamente a sí mismo, como demuestra el siguiente fragmento de código:

static int sum (int limit) { if (limit == 1) return 1; else return limit + sum (limit - 1); }

Para entender como funciona la recursión, considere los siguientes ejemplos:

1. sum (1): El método detecta que limit contiene 1 y vuelve. 2. sum (2): Como limit contiene 2, se ejecuta return limit + sum (limit - 1);. Lo que implica que

se ejecute return 2 + sum (1);. La llamada a sum (1) devuelve 1, lo que hace que return 2 + sum (1); devuelva 3.

3. sum (3): Como limit contiene 3, se ejecuta return limit + sum (limit - 1);. Esto implica que se ejecute return 3 + sum (2);. La llamada a sum (2) ejecuta return 2 + sum (1);. Luego, sum (1) devuelve 1, lo que hace que sum (2) devuelva 3, y al final return 3 + sum (2); devuelve 6.

Cuidado: Asegúrese siempre que un método recursivo tiene una condición de parada (como if (limit == 1) return 1;). Por el contrario, la recursión continuará hasta que se sobrecargue la pila de llamadas a métodos.