Vous êtes sur la page 1sur 200

ESTRUCTURAS DE

DATOS

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 1

TEMA 1.

Introduccin a la programacin.
1.1. CONCEPTOS BSICOS.
Un programa es un conjunto ordenado de instrucciones, que se aloja en la memoria
del ordenador. Estas instrucciones operan sobre datos, normalmente (aunque no
necesariamente) procedentes del mundo exterior a dicha memoria: usuarios, dispositivos de
almacenamiento (electrnicos, magnticos, pticos, etc), redes de comunicaciones, etc, para
producir resultados tanto en forma de acciones como de nuevos datos (informacin).
Acciones
Datos

ORDENADOR
(memoria)

Resultados

PROGRAMA
Informacin

Figura 1.1. Esquema de funcionamiento de un programa.


Adoptamos la definicin de Wirth (1985) para programa como conjunto de
algoritmo ms estructuras de datos.
La estricta materializacin del esquema anterior implicara que el programa debera
estar escrito utilizando un lenguaje binario (de mquina): tanto el algoritmo (haciendo
referencia al repertorio de instrucciones -instruction set- del ordenador, concretamente de
su CPU) como los datos (adecuadamente codificados).
Salvo en una efmera temporada, coincidente con la aparicin de los primeros
ordenadores (1944), apenas se ha utilizado esta modalidad de programacin como

2 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

consecuencia de su extrema dificultad


software1.

y escasa productividad para el desarrollo de

La alternativa por la que se opt fue escribir programas en un lenguaje de ms alto


nivel (lenguaje de programacin2). Uno de estos lenguajes, de gran popularidad
actualmente, es el Java.
La programacin consiste en la actividad de escribir programas utilizando para ello
lenguajes de programacin adecuados.
Para que un programa escrito (codificado) en un lenguaje de programacin
(programa fuente) pueda ejecutarse en un ordenador es necesario traducirlo a su lenguaje
(de mquina). Para ello hay que utilizar los programas adecuados, denominados
genricamente traductores.
La figura siguiente muestra un esquema de funcionamiento3:
Programa
fuente

Ordenador

TRADUCTOR

Programa
ejecutable

Figura 1.2. Esquema de funcionamiento de un traductor.


La figura anterior se corresponde con una de las dos filosofas4 en la que se basan los
traductores: la compilacin. Mediante ella se genera un cdigo denominado objeto, que,
una vez cargado (load) en la memoria del ordenador, se puede ejecutar. La compilacin
solo se realiza una vez (siempre que no se hagan cambios en el programa). El resultado
(programa ejecutable) puede guardarse en un dispositivo de almacenamiento externo
(disco, CD, etc.) y desde l cargarse en memoria y ejecutarse en futuras ocasiones.
La otra alternativa, denominada interpretacin (que no genera cdigo objeto),
funciona de manera que el programa traductor traduce y ejecuta las lneas del cdigo fuente
de una en una.
El Java es un lenguaje mixto: primero se compila (por medio del compilador Javac,
produciendo ficheros con extensin .class), y a continuacin, ese cdigo se interpreta en el

Por el contrario, sera la tcnica ms eficiente (mayor velocidad de ejecucin y menor consumo de
memoria). No obstante, y como consecuencia de la incesante mejora y abaratamiento de la tecnologa, esta
alternativa no ha sido nunca tomada en consideracin.
2
Difcilmente se podr encontrar otra actividad ms viva que el desarrollo de lenguajes de programacin:
en tan solo cincuenta aos de existencia de los ordenadores y de la Informtica algunos autores llegan a
identificar en torno a medio millar de ellos. Se puede encontrar una referencia en:
http://www.lenguajesdeprogramacion.com/
3
Se trata de un modelo muy elemental. La realidad es algo ms compleja.
4
Existen otras soluciones intermedias.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 3

ordenador en el que se va a ejecutar el cdigo (por medio de la JVM, Java Virtual


Machine).
1.1.1. Caractersticas especficas del lenguaje Java.
1.1.1.1. Clases y objetos.
Una clase es una agrupacin de variables miembro (o campos) y mtodos miembro
que operan sobre dichos datos y permiten comunicarse con otras clases: una clase puede ser
equivalente a un tipo de datos creado por el usuario junto las operaciones que se pueden
realizar sobre dicho tipo de datos. Una variable de dicho tipo de datos es un objeto o
instancia de la clase.
A los campos de un objeto se les denomina variables miembros de objeto, y a todos
aquellos mtodos que se definen dentro de la clase se les denomina mtodos miembro.
Para lograr el encapsulamiento en Java, se utiliza un tipo especial de clase: el interfaz
(interface). Una interfaz es un conjunto de declaraciones de funciones. Si una clase
implementa (implements) una interfaz, debe definir todas las funciones especificadas por la
interfaz. Una clase puede implementar ms de una interfaz.
1.1.1.2. Mtodos.
Dentro de una clase en la que se define una estructura de datos, se pueden definir
mtodos de objeto, que se aplican a una variable de la clase poniendo el nombre de la
variable y luego el nombre del mtodo, separados por un punto.
Adems de definir mtodos de objeto, dentro de la clase se pueden crear mtodos con
el modificador static: son mtodos de clase, que se pueden utilizar aunque no se haya
creado ningn objeto de la clase (al contrario de lo que ocurre con los mtodos de objeto: si
se intentan invocar sin haber creado previamente el objeto se produce un error). Los
mtodos static se invocan desde fuera de la clase poniendo el nombre de la clase y luego el
nombre del mtodo, separados por un punto.
1.1.1.3. Modificadores.
Las variables miembro pueden ir precedidas en su declaracin por uno de los
modificadores de acceso: public, private, protected y package (este ltimo es el valor por
defecto y se puede omitir). A su vez las clases pueden ser public (accesibles desde
cualquier otra clase, siempre que el paquete est en un directorio accesible) o package (por
defecto, accesible solo para el resto de las clases del paquete).
El significado de los modificadores de acceso (para variables miembro y mtodos
miembro) es el siguiente:
o private: solo son accesibles dentro de la propia clase.
o package: tienen acceso todas las clases del resto del paquete.

4 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

o protected: tienen acceso las clases del resto del paquete, as como desde
subclases definidas fuera del paquete.
o public: se puede acceder desde cualquier otra clase.
1.1.1.4. Excepciones.
Las excepciones son elementos que almacenan informacin para detectar hechos
excepcionales, como por ejemplo errores. En Java existen muchos tipos de excepciones
estndar y las ms habituales son del tipo IOException (errores de lectura), as como del
tipo NumberFormatException (que se genera cuando se espera leer un nmero y se recibe
algo diferente).
1.1.2. Entorno de programacin (IDE).
Para realizar la actividad de programacin se requiere, adems del correspondiente
programa traductor (compilador o intrprete), un conjunto de herramientas (software
development tools) que proporcionan funcionalidades tales como escribir / modificar
(editar) el cdigo fuente, facilitar la puesta a punto de programas (debugger), cargar
(load), montar (link) y ejecutar (run) programas, gestionar ficheros con cdigo fuente y
ejecutable, etc. Normalmente estas herramientas se ofrecen en forma de paquete integrado
(Integrated Development Environment IDE-). Uno de ellos, de libre distribucin para
programacin en Java es Eclipse (http://www.eclipse.org, donde adems de descargar la
aplicacin, se pueden encontrar distintos recursos como por ejemplo tutoriales para
aprender el manejo del entorno).

Figura 1.3. Entorno de programacin Eclipse.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 5

1.2. SINTAXIS DEL LENGUAJE JAVA.


Java es un representante de la familia de los lenguajes orientados a objetos.
1.2.1. Estructura de un programa en Java.
A continuacin se muestra un incipiente programa en Java sobre el que se indican los
aspectos ms importantes.
Cdigo
//Primer programa en Java
public class hola_EUI
{
public static void main (String [ ] args)
{
System.out.println ("Hola EUI");
}
}

Explicacin
Lnea de comentario.
Se indica el nombre del programa.
Delimitador de inicio del programa.
Clase principal
Delimitador de inicio de la clase
Ejemplo de accin: sentencia println (escribir en el dispositivo
de salida system.out y avanzar al comienzo de la lnea
siguiente) que acta sobre un sujeto (dato): la cadena de
caracteres Hola EUI.
Delimitador de fin de clase.
Delimitador de fin de programa.

Tabla 1.1. Estructura bsica de un programa en Java.


La estructura general de un programa en Java es la siguiente5:
Sintaxis

Semntica

import <nombre>;
public class <nombre de la clase general>
{

Llamada a una (o varias) componente(s) de la biblioteca.


Nombre de la clase general (obligatorio)
Principio de la clase.

<constantes y variables globales>


<mtodos6>
public static void main (String [ ] args)
{

Declaracin del programa principal


Inicio del programa principal

<cuerpo del programa principal>


}
}

Fin del programa principal.


Fin de la clase.

Tabla 1.2. Estructura general de un programa en Java.


La sintaxis del lenguaje Java es de formato libre7. Para identificar sus partes se
utilizan delimitadores. El delimitador ms importante es el punto y coma (;), que se utiliza
como fin de instruccin. As mismo las llaves ({ }), se utilizan para indicar el inicio y final

Este esquema se ampliar en el punto de Programacin modular (1.3)


Devuelve/n uno o ningn (void) valor/es. En Java no existe la posibilidad de devolver varios valores.
7
La otra alternativa (utilizada por lenguajes tales como COBOL o FORTRAN) se basa en considerar lneas
de cdigo de una longitud determinada estructuradas en campos, cada uno de los cuales tiene un significado
predeterminado.
6

6 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

de bloques de instrucciones o del programa. Estos (u otros) delimitadores pueden tener un


significado particular en diferentes contextos.
Generalmente una aplicacin se desarrolla en varios ficheros, que se agrupan en
packages, que son libreras de clases. Cada uno de los ficheros que la componen debe tener
la extensin .java, y la aplicacin se ejecutar por medio de la clase que contenga la
funcin main.
1.2.2. Nomenclatura habitual en Java.
Los nombres en Java son sensibles a las maysculas y las minsculas (por ejemplo,
cadena sera una variable distinta de Cadena). Habitualmente en Java se escribe con
minsculas, con las siguientes excepciones:
o Cuando un nombre consta de varias palabras, se escriben seguidas, comenzando
en minsculas, excepto la primera letra de cada palabra a partir de la segunda (por
ejemplo presuntoDivisor, datoMayor, etc.).
o Los nombres de las clases e interfaces empiezan con mayscula (por ejemplo
Pila, Lista, Arbol).
o Los nombres de objetos, variables y mtodos empiezan por minscula (por
ejemplo buscarMayor, maximaDiferencia, etc.).
o Los nombres de las constantes (o variables finales) se escriben en maysculas
(por ejemplo PI).
1.2.3. Los datos.
Como ya se ha indicado, los datos son los sujetos del tratamiento. Los datos, en un
programa se pueden presentar en tres modalidades:
o En forma literal. Es decir, indicando de forma explcita el propio dato. Por
ejemplo, la cadena de caracteres Hola EUI en el ejemplo anterior.
o Mediante un identificador de constante. Si se ha dado un nombre simblico al
valor de un dato (por ejemplo: PI = 3.14), el programa puede hacer referencia a l
mediante el identificador asociado.
o Mediante un identificador de variable. Se trata de un caso parecido al de
constantes, con la importante diferencia que el valor asociado al identificador de
la variable (en adelante, simplemente, variable) puede cambiar durante la
ejecucin del programa.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 7

1.2.3.1. Tipos de datos.


El lenguaje Java reconoce de forma predefinida los siguientes tipos8 de datos:
Numricos:
o Enteros (int). En el rango, aproximadamente, 2*109. Adems existen otros tipos
de datos enteros: byte, short y long.
o Racionales o fraccionarios (float). Aproximadamente en el rango 3.4*1038. La
parte real y la fraccionaria se separan mediante un punto (.). Tambin se pueden
utilizar nmeros reales con mayor precisin por medio del tipo double
(aproximadamente en el rango 1.8*10308).
Lgicos (boolean). Se refieren a los valores utilizados en el lgebra de Boole
(verdadero: true o falso: false).
Carcter (char). Se utiliza para representar todos los caracteres del estndar Unicode
(que incluye el ASCII).
Cadena de caracteres (String). Este tipo de datos consiste en una secuencia de
caracteres (por ejemplo El cielo est enladrillado).
1.2.3.2. Tratamiento de datos.
1.2.3.2.1. Operaciones.
Una operacin es un proceso que acta sobre dos datos (operandos) y produce, en
consecuencia, un resultado. Se identifica mediante un smbolo (o palabra reservada)
denominado operador. Excepcionalmente se pueden presentar operaciones (unarias) que
implican un solo operando y/u operaciones con datos de diferentes tipos9. Normalmente
(pero no siempre) se utilizan dos operandos del mismo tipo que, adems, es el tipo del
resultado.
Como consecuencia de la naturaleza (tipo) de un dato, tendr sentido realizar cierto
tipo de operaciones con l. La tabla siguiente muestra las ms habituales:

El concepto tipo de datos se asocia implcitamente al los datos elementales (simples). Ms adelante
(apartado 1.4) se contemplar la posibilidad de agrupar datos simples en unidades de orden superior para dar
lugar a diferentes estructuras de datos.
9
Las operaciones con ms de dos operandos las consideramos dentro del epgrafe de expresiones.

8 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Tipo

Operacin

Operador

Entero (int)

Suma
Resta
Multiplicacin
Cociente
Resto
Suma
Resta
Multiplicacin
Divisin
Negacin
Unin (or)
Interseccin (and)
No existen
Concatenacin

+
*
/
%
+
*
/
!
||
&&

Ejemplo
Operacin
3+5
1-3
2 * (-5)
7/3
7%3
1,23 + 8,77
1,23 - 8,77
-3,2 * 4,6
3,2 / (-4,6)
! true
true || false
true && false

Hola + Antonio

Real (float)

Lgico (boolean)

Carcter (char)
Cadena (String)

Resultado
8
-2
-10
2
1
10,0
-7,54
-14,72
-0,6956
false
true
false
10

Hola Antonio

Tabla 1.3. Operaciones bsicas.


Operaciones de relacin.
Se trata de un tipo especial de peraciones en donde los operandos son del mismo
tipo y el resultado es de tipo lgico (boolean). Su interpretacin es ligeramente distinta
en funcin del tipo de operandos.
11

Operador
>
<
==
>=
<=
!=

Tipos numricos (int o float)


Significado
Ejemplo
Mayor que
7>3
Menor que
7<3
Igual a
7 == 3
Mayor o igual que 3 >= 3
Menor o igual que (-3) <= 3
Distinto de
3 != 3

Result.
true
false
false
true
true
false

Tipo carcter (char)


Significado
Ejemplo
Posterior a
a > z
Anterior a
P < p
Igual a
a == A
Posterior o igual a z >= m
Anterior o igual a
M <= N
Distinto de
p !=P

Result.
false
12
true
false
true
true
true

Tabla 1.4. Operaciones de relacin.


1.2.3.2.2. Funciones estndar y complementos.
En trminos generales se define una funcin como un tratamiento que se realiza
sobre ninguno, uno o varios operandos (argumentos) y produce (devuelve) un resultado.
Los lenguajes de programacin incluyen un conjunto ms o menos amplio de
funciones13 que se identifican como palabras reservadas (forman parte del lenguaje) y

10

Obsrvese que la cadena (String) Antonio incluye un espacio en blanco a su izquierda.


Excepto de tipo lgico (boolean) en donde no tiene sentido.
12
Los cdigos Unicode de las letras maysculas son ms bajos que los de las minsculas.
13
La diversidad de oferta de funciones integradas en el lenguaje es muy dispar, no solo entre diferentes
lenguajes sino, tambin, para un mismo lenguaje, entre diferentes desarrolladores de traductores. Para su
eficiente utilizacin se insta encarecidamente a utilizar el manual del fabricante. En este sentido su empleo
debe entenderse como manejo de catlogo, dado que la aportacin conceptual es escasa.
11

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 9

deben emplearse siguiendo la sintaxis adecuada: <nombre de la funcin> (<arg1>,


<arg2>, <argN>).
Algunas funciones estn disponibles como parte integrante del propio lenguaje en
tanto que otras no forman parte del lenguaje. En el caso de Java, las correspondientes
funciones (mtodos) estn declaradas y definidas en componentes (en Java, paquetes o
packages) que, en su conjunto, configuran una biblioteca, almacenadas en uno o varios
directorios14.
Ejemplos:
Algunos de los mtodos de la clase MATH son:
o Valor absoluto. (abs). Su sintaxis es abs (<tipo int|float>). Devuelve un
nmero (integer o real, segn proceda, positivo). Ejemplos:

abs(-3.25) devuelve el valor 3.25.

abs(77) devuelve el valor 77.

o Nmero aleatorio (random). No utiliza argumentos. Devuelve un nmero


racional (real) en el rango 0 < n < 1. Ejemplo: random puede devolver
0,67635
o Redondeo (round), Devuelve el entero ms cercano al argumento.
La clase String tiene entre otros los siguientes mtodos:
o length () devuelve la longitud de una cadena ().
o toLowerCase () devuelve una cadena con todas las letras en minsculas,
o toUpperCase () devuelve una cadena con todas las letras en maysculas
o subString (int, int), devuelve un subString extrado de otro.
La clase Integer (es un wrapper del tipo primitivo int: los wrappers se utilizan para
poder utilizar mtodos como conversin de cadenas de caracteres a enteros y
viceversa), tiene entre otros los siguientes mtodos:
o parseInt (String) convierte una cadena a un nmero entero,
o toString () convierte un nmero entero a una cadena,
o MAX_VALUE, MIN_VALUE, constantes predefinidas que devuelven el
valor mayor y menor de un entero.
Finalmente, usted tambin puede desarrollar sus propias libreras (packages),
almacenarlas en el directorio que considere oportuno, indicarle dicha ubicacin al
compilador y utilizar las correspondientes operaciones (Ver Tema 2 Tipos
Abstractos de Datos).

14

Adems de las componentes (clases) proporcionadas por el fabricante (en este caso Eclipse) el
programador puede incorporar otras de desarrollo propio (ver tema 2: Tipos Abstractos de Datos) o
adquiridas a terceros.

10 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

1.2.3.2.3. Expresiones.
Una expresin es una combinacin (coherente) de datos (literales, constantes y/o
variables), operaciones y funciones, enlazados entre s por medio de operadores.
Como consecuencia de evaluar una expresin, siguiendo las reglas
correspondientes, se obtiene un resultado de tipo acorde con la naturaleza de la expresin.
1.2.4. Sentencias.
Las sentencias son construcciones sintcticas que indican al ordenador qu
operaciones debe realizar y, normalmente, con qu datos. Materializan la parte operativa
de un programa que implementa el algoritmo (definicin de Wirth, 1985) que se deber
ejecutar15. Dicha parte constituye un bloque delimitado por { y }.
Se describe a continuacin, la sintaxis de las sentencia de Java, agrupadas en funcin
de su naturaleza.
1.2.4.1. Asignacin.
Permite, como su propio nombre indica, asignar un valor a una variable (ver 1.2.2)
definida en la parte declarativa. Su sintaxis es:
16

<nombreVariable> = <expresin >;

Ejemplo (HolaEui2.Java):
//Ejemplo de asignacion a variables.
public class HolaEui2 {
public static void main (String [ ] args) {
String cadena = "Hola EUI";
System.out.println (cadena);
}
}

1.2.4.2. Entrada y salida17.


Para realizar la entrada y salida de datos se utiliza la clase Java.io. La entrada
estndar es System.in y la salida estndar es System.out. Para realizar la entrada/salida se
utilizan flujos de datos (streams). Un stream es una conexin entre el programa y la fuente
o destino de los datos. Las sentencias principales son readline (entrada) y print o println
(salida).
La sentencia print, cuya sintaxis bsica es:
System.out.print (<expresin>);

15

Con excepcin de comentarios y Directivas de compilacin. Consultar el manual del compilador.


Ver epgrafe Expresiones.
17
Hacen referencia a los dispositivos configurados por defecto (el teclado y la pantalla).
16

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 11

Hace que se muestre en la pantalla (System.out representa la salida estndar, es decir,


la pantalla) el resultado de evaluar la <expresin> utilizada como argumento. Dicho
argumento debe ser un String, que puede estar formado por la concatenacin (con +) de
varias cadenas de caracteres diferentes. Si se incluye un nmero o valor lgico dentro de la
expresin, se convertir automticamente a un String (por medio de los wrappers: las clases
Integer, Float, Boolean etc.).
Si en vez de utilizar print, utilizamos println, se incluir un salto de lnea despus de
<expresin>.
La sentencia readline, cuya sintaxis bsica es:
BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));
String cadena;
.

cadena = linea.readLine();
.

implica la realizacin de la siguiente secuencia de acciones:


En las dos primeras lneas, se declaran los objetos linea del tipo
BufferedReader y cadena de tipo String.
El usuario escribe los caracteres correspondientes y finaliza pulsando la tecla
Intro.
Se utiliza el mtodo readLine sobre el objeto linea, y se almacena en cadena.
Se contina con la ejecucin del programa.
Para leer otros datos que no sean de tipo String, habra que utilizar una sintaxis
similar a la siguiente:
BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));
int n;
.

n = Integer.parseInt (linea.readLine());
.

que implica la realizacin de la siguiente secuencia de acciones:


En la primera lnea, se declara el objeto linea del tipo BufferedReader.
El usuario escribe los datos correspondientes y finaliza pulsando la tecla
Intro.
Se utiliza el mtodo readline sobre el objeto linea, y como se espera la
introduccin de un dato de tipo int, se convierte la lnea leda (de tipo String),
al tipo entero mediante el mtodo parseInt de la clase Integer.
Se contina con la ejecucin del programa.
A continuacin se muestra un ejemplo (HolaEui3.Java) que contempla la mayora de
consideraciones explicadas hasta el momento.

12 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

import java.io.*;
//Ejemplo de sentencias de entrada/salida.
public class HolaEui3 {
public static void main(String [] args) throws
NumberFormatException,IOException{
String cadena, cadena2;
int edad, ao = 2010;
BufferedReader linea=new BufferedReader (new InputStreamReader (System.in));
System.out.println ("Hola, como te llamas? ");
cadena = linea.readLine();
System.out.println ("Encantado de conocerte, " + cadena);
System.out.println ("Cuantos aos tienes? ");
edad = Integer.parseInt (linea.readLine());
edad = edad+3;
ao = ao+3;
cadena2 = cadena+", en algun momento de "+ao+" tendrs "+edad+" aos";
System.out.println (cadena2);
System.out.println (cadena);
}
}

La figura siguiente (consola de eclipse) muestra un posible resultado de su ejecucin.

Figura 1.4. Ejecucin del ejemplo HolaEui3.pas.


1.2.4.3. Control de flujo.
En los ejemplos anteriores se ha mostrado una de las tres estructuras consideradas en
el enfoque (paradigma) de programacin estructurada: la secuencia. El ordenador ejecuta
(salvo imprevistos) una sentencia tras otra.
Las dos estructuras restantes suponen una ejecucin no lineal del conjunto de
sentencias respecto al orden en que aparecen escritas en el programa. Son las estructuras
alternativa y repetitiva (o iteracin).

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 13

1.2.4.3.1. Estructura alternativa.


Lo que se ejecute depender del resultado de evaluar una operacin de relacin
(<condicin>).
Alternativa simple (if..else).
En su modalidad ms sencilla la estructura alternativa se regula mediante la
sentencia if, cuya sintaxis18 es:
if (<condicion>) {
<grupo de sentencias>;
}
else {
<grupo de sentencias>;
}

Ejemplo:
El siguiente programa informa al usuario de la paridad de un nmero entero
introducido por el teclado.
import java.io.*;
//Ejemplo de alternativa simple.
public class PruebaIf {
public static void main (String [] args) throws NumberFormatException,
IOException {
int dato;
BufferedReader linea=new BufferedReader (new InputStreamReader (System.in));
System.out.print("Numero: ");
dato = Integer.parseInt (linea.readLine());
if ((dato % 2) == 0)
System.out.println (" es par");
else System.out.println (" es impar");
}
}

Operador condicional
El operador condicional ?: se puede usar como abreviatura de la sentencia if, cuya
sintaxis es:
expresionCondicional ? expresionSI : expresionNO;

Primero se evala expresionCondicional. Si es true, se devuelve expresionSI, en


caso contrario se devuelve expresionNO. Por ejemplo, si queremos guardar el valor
menor entre x e y en la variable minimo podramos hacer:
minimo = x <= y ? x : y;

18

Se consideran los siguientes casos particulares:


<grupo de sentencias> puede ser una nica sentencia, y no haran falta las llaves ({ y }).
Se puede omitir else: el ordenador no realizara ninguna accin y ejecutara la siguiente sentencia.

14 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Alternativa mltiple (switch).


La ejecucin del programa contina por una de entre varias opciones en funcin
del resultado de evaluar una variable (de tipo char o int). Para utilizar esta estructura
se utiliza la sentencia switch, cuya sintaxis general es:
switch (<variable>) {
case <valor1>:
<grupo de sentencias1;>
break;
.
case <valorN>:
<grupo de sentenciasN;>;
break;
default
<grupo de sentenciasD;>
break;
}

Ejemplo:
El programa siguiente espera recibir desde el teclado un dgito, entre 1 y 7,
correspondiente al ordinal del da de la semana (lunes a domingo) y mostrar por la
pantalla el literal del da asociado o un mensaje de error si el cdigo no es vlido.
import java.io.*;
//Ejemplo de alternativa multiple.
public class PruebaSwitch {
public static void main (String [ ] args) throws NumberFormatException,
IOException {
BufferedReader linea = new BufferedReader (new InputStreamReader
(System.in));
System.out.print ("Opcion: ");
int opc = Integer.parseInt (linea.readLine ());
switch (opc) {
case 1: System.out.println ("lunes");
break;
case 2: System.out.println ("martes");
break;
case 3: System.out.println ("miercoles");
break;
case 4: System.out.println ("jueves");
break;
case 5: System.out.println ("viernes");
break;
case 6: System.out.println ("sabado");
break;
case 7: System.out.println ("domingo");
break;
default: System.out.println ("opcion no valida");
break;
}
}
}

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 15

1.2.4.3.2. Estructura iterativa.


Consiste en realizar la ejecucin de un bloque de cdigo varias veces. Las
modalidades que se explican a continuacin dependen de la forma en que se realiza el
control del nmero de repeticiones.
Estructura iterativa controlada por contador (for).
Se utiliza una variable entera que acta como contador. La sentencia indica el
valor inicial del contador, hasta cuando se debe realizar el bucle, as como la cuanta
en que se incrementa (o decrementa) el contador. Su sintaxis es:
for (<val._inicicial> ; <comprobacion>;<incremento>) {
<grupo de sentencias;>
}

Ejemplo:
El siguiente programa muestra por la pantalla el valor de los cuadrados de 5
nmeros introducidos por el teclado.
import java.io.*;
//Ejemplo de bucle for.
public class PruebaFor {
public static void main(String[] args) throws NumberFormatException,IOException{
int i, dato, cuadrado;
BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));
for (i = 1; i <= 5; i ++) {
System.out.print ("Numero: ");
dato = Integer.parseInt (linea.readLine ());
cuadrado = dato * dato;
System.out.println ("El cuadrado de " + dato + " es: " + cuadrado);
}
}
}

Estructura iterativa controlada por condicin.


En los casos siguientes la repeticin, o no, de la ejecucin de un bloque de cdigo
es consecuencia del resultado de evaluar una expresin booleana (<condicin>).
o Estructura mientras (while).
Se utiliza la sentencia while, cuya sintaxis es:
while (<condicion>) {
<grupo de sentencias;>
}

La evaluacin de la <condicin> es previa19 a la ejecucin del bloque de


cdigo que se repite.

19

En consecuencia, podra ser que el bloque de cdigo pudiera no ejecutarse ninguna vez.

16 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Ejemplo:
El siguiente programa ejecuta reiteradamente el ejemplo de los das de la
semana, salvo que el usuario teclee 0 como opcin20.
import java.io.*;
//Ejemplo de bucle while.
public class PruebaWhile {
public static void main (String [] args) throws NumberFormatException,
IOException {
int opc;
BufferedReader linea = new BufferedReader (new InputStreamReader
(System.in));
System.out.print ("Opcin: ");
opc = Integer.parseInt (linea.readLine());
while (opc != 0) {
switch (opc) {
case 1: System.out.println ("lunes");
break;
case 2: System.out.println ("martes");
break;
case 3: System.out.println ("miercoles");
break;
case 4: System.out.println ("jueves");
break;
case 5: System.out.println ("viernes");
break;
case 6: System.out.println ("sabado");
break;
case 7: System.out.println ("domingo");
break;
default: System.out.println ("opcin no valida");
break;
}
System.out.print ("Opcin: ");
opc = Integer.parseInt (linea.readLine ());
}
}
}

o Estructura Repetir mientras (do while).


Se utiliza la sentencia do ... while, cuya sintaxis es:
do
while

<grupo de sentencias;>
(<condicin>);

La evaluacin de la <condicin> es posterior21 a la ejecucin del bloque de


cdigo que se repite.

20
21

Pruebe a ejecutarlo introduciendo la primera vez la opcin 0.


En consecuencia, la ejecucin del bloque tiene lugar, al menos, una vez.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 17

Ejemplo:
El programa siguiente constituye una variante del ejemplo anterior en el que
el bloque de cdigo se ejecuta una vez y volver a repetirse reiteradamente, o
no, hasta que el usuario teclee 0 como opcin22.
import java.io.*;
//Ejemplo de bucle do ... while.
public class PruebaDoWhile {
public static void main (String [] args) throws NumberFormatException,
IOException {
int opc = 7;
BufferedReader linea=new BufferedReader (new InputStreamReader (System.in));
System.out.println("Empezamos en domingo");
do {
switch (opc) {
case 1: System.out.println ("lunes");
break;
case 2: System.out.println ("martes");
break;
case 3: System.out.println ("miercoles");
break;
case 4: System.out.println ("jueves");
break;
case 5: System.out.println ("viernes");
break;
case 6: System.out.println ("sabado");
break;
case 7: System.out.println ("domingo");
break;
default: System.out.println ("opcin no valida");
break;
};
System.out.print ("Opcin: ");
opc = Integer.parseInt (linea.readLine ());
} while (opc != 0);
}
}

22

Pruebe a ejecutarlo introduciendo la primera vez la opcin 0.

18 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

1.3. PROGRAMACIN MODULAR. SUBPROGRAMAS.


1.3.1. Reflexin previa.
Cualquier producto de Ingeniera (desde un barco hasta una central nuclear, pasando
por una catedral) se concibe como un sistema complejo constituido por un conjunto de
subsistemas ms sencillos, y as sucesivamente hasta acabar en piezas indivisibles.
El plantearse las cosas de esta manera permite que un conjunto de profesionales
especializados pueda realizar la obra, con unos costes y tiempos razonables, y que, una vez
finalizada, se puedan acometer con eficiencia las inevitables tareas de actualizacin y
conservacin.
Inexplicablemente, con demasiada frecuencia, nos cuesta transmitir a algunos
alumnos que la filosofa expuesta es (como no poda ser de otra manera), tambin, aplicable
a las obras producidas mediante la Ingeniera del Software. Sostienen que como, de
momento, las aplicaciones que desarrollan en sus actividades prcticas no son de gran
volumen, se acaba antes programando de un tirn y que el da que les toque trabajar en
un proyecto de centenares de millares de lneas de cdigo ya cambiarn de actitud, etc.
Evidentemente, cada cual es libre de equivocarse voluntariamente siempre que as lo
desee.
1.3.2. Tecnologa para la programacin modular.
Cuando hemos hablado antes de programacin modular lo hemos hecho desde una
perspectiva de disciplina mental. Por supuesto que la tecnologa proporciona un
importante conjunto de facilidades para dar soporte a tales concepciones. Aunque su
explicacin pormenorizada queda fuera de los objetivos y alcance de este curso, citaremos,
simplemente, a grandes rasgos las principales variantes:
Mdulos externos. La fuente de procedencia de tales mdulos es diversa:
construidos previamente por el propio programador (o su empresa), incluidos en el
propio entorno de programacin (paquetes o packages en el caso del entorno de
programacin de Java utilizado en el curso) o adquiridos a terceros23. El
programador de aplicaciones, para utilizar dichos mdulos deber nicamente
incorporarlos, indicar al compilador, dnde est/n ubicado/s y saber de que
funcionalidades dispone y que sintaxis hay que emplear.

23

Internet es una fuente casi inagotable donde obtener este tipo de recursos.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 19

Mdulos desarrollados por el propio programador almacenados como ficheros


independientes de la aplicacin principal. A todos los efectos su funcionamiento es
anlogo al caso de los mdulos externos24.
Mdulos desarrollados por el propio programador y almacenados junto con la
aplicacin principal25 en un nico fichero.
Existen varias alternativas tecnolgicas a la hora de gestionar la memoria del
ordenador cuando ejecuta una aplicacin concebida en forma modular. La ms sencilla es la
que aparece ilustrada en la figura siguiente.
Memoria libre
Zona de datos

Zona de
algoritmo

Subprograma (mdulo)
Zona de datos

>

<retorno
Zona de
algoritmo

---------------------

<Llamada>
---------------------

Programa principal

Figura 1.5. Gestin de la memoria en programas modulares-.


Este tipo de gestin de memoria se denomina esttica26 porque durante todo el tiempo
de ejecucin tanto el programa principal como la totalidad de mdulos estn cargados en
memoria, alojados en espacios independientes.
A su vez, cada uno de los espacios se subdivide en otros dos: zona de datos y zona de
algoritmo (la parte de cdigo -previamente traducido a lenguaje de mquina-).
La ejecucin del conjunto tiene lugar a partir de la primera instruccin del programa
principal. Cuando se llega a una <llamada> se suspende y se pasa al contexto del

24

La mayora de entornos de programacin modernos contemplan el concepto de proyecto en donde se indica


el conjunto de mdulos que configuran el sistema y sus ubicaciones. En el curso, dado su carcter bsico, no
utilizaremos esta posibilidad.
25
Se entiende por aplicacin principal a la parte de cdigo (generalmente poco extensa) que utiliza (llama o
invoca) los diferentes mdulos funcionales.
26
Las alternativas ms evolucionadas son de naturaleza dinmica: el bloque correspondiente al programa
principal est permanentemente en memoria mientras que los subprogramas se cargan cuando se necesitan y
al finalizar su ejecucin se libera la memoria ocupada. No obstante esta tcnica es la nica posible cuando se
utiliza el tratamiento recursivo (ver apartado 1.2.5.5)

20 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

subprograma, hasta que finalice su ejecucin, a partir de entonces se reanuda la ejecucin


del programa principal en el punto en que qued suspendida.
1.3.3. Mecanismos para el paso de informacin27.
Cuando un programa llama a un mdulo deber utilizar un nombre que lo identifique
y, adems, pasarle los datos que necesite. El conjunto de estos datos se denomina
genricamente parmetros o argumentos y se expresa mediante las correspondientes
variables y su tipo. Existen dos modalidades de paso de argumentos:
Por valor. La variable del subprograma recibe inicialmente una copia del valor
almacenado, en el momento de producirse la llamada, en el mdulo principal.
Durante su ejecucin (del subprograma) podr modificarlo pero una vez que
finalice, el programa principal conserva intacto su valor. En Java todos los tipos
primitivos (nmeros enteros, reales, caracteres y booleanos) se pasan siempre por
valor.
Por referencia. En este caso no existe tal variable en el mbito del subprograma. El
mecanismo consiste en que el mdulo principal permite al subprograma actuar
sobre sus propias variables. En consecuencia, cualquier modificacin que se
realice en una variable pasada por referencia tendr efecto en el contexto del
programa (mdulo) principal. En Java todos los datos que no sean de un tipo
primitivo se pasan por valor, pero al ser referencias, los datos en s pueden cambiar
(pero no la posicin de memoria en la que estn guardados).
Adems hay que considerar los siguientes aspectos:
Un subprograma puede declarar sus propias variables locales28.
Es posible, pero no recomendable29 (salvo contadas excepciones), declarar una
variable como accesible para el programa principal y la totalidad de los
subprogramas30. Este tipo de variables se denominan globales31.

27

Lo que se explica en el apartado debe entenderse con carcter general. El lenguaje Java solo
admite el paso de argumentos por valor. Esta afirmacin es extensiva al caso de que el argumento sea un
objeto (por ejemplo, un vector) dado que lo que se pasa como argumento por valor es un puntero (referencia)
a dicho objeto que no puede ser modificado por el mdulo subordinado (aunque s el propio objeto).
28
No hay ningn inconveniente en que dos variables locales de mdulos diferentes tengan el mismo
nombre.
29
Una modificacin de una variable global realizada por un mdulo afectar al resto.
30

En este caso si habra ambigedad en caso de coincidencia de nombres de una variable local y otra
global. Esto no supone ningn error de compilacin y la ejecucin podra producir resultados no esperados (ni
deseados).
31
No tiene sentido pasar como argumentos variables globales. Ya estn accesibles desde todos los
mdulos.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 21

Que el programa funcione (correctness) es una condicin necesaria, pero no suficiente.


Existen otros criterios: eficiencia, seguridad, fiabilidad, mantenibilidad, legibilidad, productividad,
posibilidad de desarrollo en equipo, etc.
En la Ingeniera del Software, debe prestarse atencin especial al uso correcto y eficiente de
los datos (la informacin) tanto o ms que al cdigo (algoritmo). A continuacin se indican algunas
recomendaciones en este sentido.
Proteccin de la informacin: evitar el uso de variables globales.
Eficiencia: no pasar ms argumentos que los estrictamente imprescindibles.
Productividad / mantenibilidad: dotar a los subprogramas de la mayor autonoma posible (no pasar
argumentos que se puedan obtener en el contexto del propio subprograma).

1.3.4. Aplicacin al lenguaje Java.


Lo que en otros lenguajes se llama funcin o procedimiento, en Java se llama mtodo.
La cabecera de un mtodo consiste en un nombre, una lista de parmetros (que puede ser
vaca), y el tipo de resultado (si no devuelve resultado, el mtodo ser de tipo void).
La sintaxis (cabecera o prototipo) en Java es:
<tipo devuelto> <nombre del mtodo> (<lista de argumentos>)

Siendo la sintaxis de <lista de argumentos> la siguiente:


<tipo> <argumento1>, <tipo> <argumento2>, ...

Los nombres de los argumentos son formales. No tienen por qu coincidir con los
nombres reales de las variables utilizadas en la llamada al mtodo32. El compilador solo
verifica que las listas de argumentos empleadas en la llamada (reales) y en la cabecera
(formales) coinciden en nmero, tipo y orden.
Las sintaxis de las llamadas es similar a la de las cabeceras, si bien en este caso se
utiliza una versin reducida de la <lista de argumentos> en la que solo aparecen sus
nombres (reales), separados por comas.
El mtodo est delimitado por las llaves de inicio y fin de bloque ({ y }), y a
continuacin aparece la declaracin de variables locales del subprograma y el algoritmo.
Los mtodos que no sean de tipo void deben acabar con la siguiente sentencia:
return <variableDeTrabajo>;

(siendo <variableDeTrabajo> el nombre de una variable local en donde se recoge el


resultado generado por el mtodo). De esta forma el mtodo devuelve el resultado
generado al mdulo de llamada.
Si el mtodo es de tipo void, se puede prescindir de la instruccin return, o utilizarla
sin ninguna variable asociada (return;).

32

El hacerlo as permite que los subprogramas sean reutilizables.

22 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Dado que el lenguaje Java solo admite el paso de argumentos por valor no es posible
que el modulo subordinado modifique una variable del mdulo de llamada (salvo que se
trate del valor devuelto por el mtodo). En tales situaciones optamos por crear una clase
esttica que declare dicha variable como global de la clase (lo que no contraviene la
recomendacin general de no utilizar variables globales)33 por lo que carecera de sentido
pasarla como argumento.
Como consecuencia de la utilizacin de la tcnica de subprogramacin, el esquema de
estructura general de un programa en Java visto en la seccin 1.2.1 queda ampliado tal
como se muestra a continuacin.
Sintaxis
import <nombre>
public class <nombre de la clase general>
{

Semntica
Llamada a una (o varias) componente(s) de la biblioteca.
Nombre de la clase general (obligatorio)
Principio de la clase.

<constantes y variables de la clase>


<tipo devuelto> <nombre del mtodo1> (<lista
de argumentos>)
{
<constantes y variables del mtodo>
....
return [<variable de trabajo>];
}
<tipo devuelto> <nombre del mtodo1> (<lista
de argumentos>)

Cabecera del mtodo 1


Inicio del mtodo 1
sentencias del mtodo 1

fin del mtodo 1


Cabecera del mtodo 2
Inicio del mtodo 2

{
<constantes y variables del mtodo>
....

sentencias del mtodo 2

return [<variable de trabajo>];

fin del mtodo 2

......
public static void main (String [ ] args)
{

Declaracin del programa principal


Inicio del programa principal

<llamadas a los mtodos>


sentencias del programa principal
}
}

Fin del programa principal.


Fin de la clase.

Tabla 1.5. Estructura de un programa en Java.

33

Algunos programadores optan por la alternativa de declarar dicha variable como vector (objeto) y pasar el
puntero (referencia) correspondiente como argumento (por valor).

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 23

1.3.4.1. Ejemplo.
El siguiente programa pide al usuario una serie de cinco nmeros enteros y muestra
por la pantalla dos de sus estadsticas: valor medio y rango.
import java.io.*;
public class PruebaMetodos {
static float media (int dato1, int dato2, int dato3, int dato4, int dato5) {
float resul;
resul = (dato1 + dato2 + dato3 + dato4 + dato5) / 5;
return resul;
}
static int max (int dato1, int dato2, int dato3, int dato4, int dato5) {
int resul;
resul=dato1;
if (dato2 > resul)
resul = dato2;
if (dato3 > resul)
resul = dato3;
if (dato4 > resul)
resul = dato4;
if (dato5 > resul)
resul = dato5;
return resul;
}
static int min (int dato1, int dato2, int dato3, int dato4, int dato5) {
int resul;
resul = dato1;
if (dato2 < resul)
resul = dato2;
if (dato3 < resul)
resul = dato3;
if (dato4 < resul)
resul = dato4;
if (dato5 < resul)
resul = dato5;
return resul;
}
public static void main(String[] args) throws NumberFormatException,IOException{
int d1, d2, d3, d4, d5, rango;
float media;
BufferedReader linea = new BufferedReader(new InputStreamReader(System.in));
System.out.println ("Introduce cinco nmeros enteros: ");
d1 = Integer.parseInt (linea.readLine ());
d2 = Integer.parseInt (linea.readLine ());
d3 = Integer.parseInt (linea.readLine ());
d4 = Integer.parseInt (linea.readLine ());
d5 = Integer.parseInt (linea.readLine ());
media = media (d1, d2, d3, d4, d5);
System.out.println("El valor medio es: " + media);
rango = max (d1, d2, d3, d4, d5) - min (d1, d2, d3, d4, d5);
System.out.println ("y el rango: " + rango);
}
}

24 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

1.3.5. Recursividad.
1.3.5.1. Concepto.
Se define objeto recursivo como: aquel que forma parte de s mismo o que forma
parte de su propia definicin.
La recursividad es un concepto matemtico que permite formular definiciones
como, por ejemplo, la de nmero natural:
o El 1 es un nmero natural.
o El siguiente de un nmero natural es un nmero natural.
Otro ejemplo sera la definicin de factorial de nmero natural:
o 0! = 1;
o
N > 0, N! = N * (N-1)!
1.3.5.2. Aplicacin del concepto de recursividad en programacin.
Los ejemplos anteriores se pueden implementar mediante programas informticos que
proporcionan un mtodo sencillo, til y potente de resolucin de infinidad de problemas.
De hecho, hay algunos cuya solucin no recursiva sera de elevada complejidad y escasa
eficiencia34. Frente a las ventajas indicadas (robustez y legibilidad) de la recursividad frente
al tratamiento iterativo se contrapone el inconveniente del mayor consumo de memoria, por
las razones que se explican a continuacin.
La recursividad en programacin se obtiene mediante subprogramas que se llaman a
s mismos (es decir, una de las sentencias del cuerpo del subprograma coincide con el
nombre del mismo), no obstante sera ms correcto decir que llama a otra copia (instancia)
del mismo, esa copia a otra nueva y as sucesivamente hasta que se alcanza una condicin
denominada de terminacin o de parada.
Una vez que se alcanza la condicin de terminacin, en la instancia n-sima se
retorna (como en todo subprograma) al punto del modulo de llamada de la instancia previa
desde donde sta se realiz, y as sucesivamente, teniendo lugar una fase de vuelta por las
instancias anteriores, hasta llegar al punto del programa principal en que se produjo la
llamada al subprograma recursivo.
La implementacin en ordenador de algoritmos recursivos requiere una tecnologa
dinmica, diferente a la esttica (explicada en el apartado 1.3.2), dado que no se sabe, a
priori, cuantas instancias se van a necesitar. Durante la fase de ida se cargan en la
memoria nuevas copias del subprograma35 hasta alcanzar la terminacin (fase de

34

Un ejemplo en este sentido sera el clsico algoritmo denominado Torres de Hanoi.


En caso de un nmero elevado de llamadas recursivas es posible quedarse sin espacio libre en memoria y se
produce un error en tiempo de ejecucin (StackOverflowError).
35

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 25

transicin). Por el contrario, durante la fase de vuelta se libera la memoria


correspondiente a cada instancia a medida que finaliza su ejecucin (ejecuta el cdigo de
retorno).
En el ejemplo siguiente el programa principal llama a un mtodo recursivo que
devuelve el factorial de un nmero natural introducido por el teclado.
import java.io.*;
public class PruebaRecursividad {
static int factorial (int dato) {
int resul = 1;
if (dato > 0)
resul = dato * factorial (dato - 1);
return resul;
}
public static void main(String[] args) throws NumberFormatException,IOException{
int d, f;
BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));
System.out.print("Introduzca el dato: ");
d = Integer.parseInt (linea.readLine ());
if (d >= 0) {
f = factorial(d);
System.out.println("El factorial de " + d + " es: " + f);
}
else System.out.println("No existe el factorial de un nmero negativo");
}
}

Explicacin:
El programa principal llama al subprograma factorial y le pasa como argumento la
variable d. Cuando ste termine de ejecutarse, el programa de llamada dispondr de
un resultado que podr mostrar por la pantalla.
El subprograma factorial es recursivo.
o

Su condicin de terminacin es dato == 0. En la instancia en que se alcance36


(transicin) devolver el valor 1.

Durante la fase de ida, la ejecucin de cada instancia consiste en multiplicar


el valor del argumento recibido desde la instancia anterior (salvo la primera,
que lo recibe directamente del programa principal) por el factorial del nmero
anterior (factorial(N-1)). La ejecucin de esa instancia queda suspendida hasta
que se le devuelva el valor solicitado.

En la fase de vuelta cada instancia:


Multiplica el valor devuelto por la instancia siguiente por el que, en su
momento de la fase de ida, recibi como argumento,

36

Su nmero de orden ser uno ms que el dato cuyo factorial se quiere calcular.

26 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

almacena el resultado en la variable local resul,


devuelve el contenido de resul a la instancia anterior (sentencia factorial =
resul) y
termina su ejecucin con } (Se libera la memoria correspondiente).
o

Observe que la palabra dato tiene dos significados en el cdigo del


subprograma recursivo:
En la llamada factorial (dato-1) se trata de una variable real. Lo que se pasa
como argumento es el valor actual decrementado en una unidad.
En la declaracin del subprograma static int factorial (int dato) es una
variable formal, debe interpretarse como: lo que se reciba.

1.3.5.3. Consideraciones complementarias.


Terminacin anticipada.
La condicin de terminacin la entendemos como pesimista en el sentido de se
alcanza cuando se han invocado todas las instancias posibles. No obstante en
determinadas circunstancias se puede producir37 una terminacin anticipada. En tal
caso lo que se debe hacer es no realizar nuevas llamadas recursivas.
Utilizacin de mdulos de llamada
En determinadas ocasiones, el subprograma recursivo requiere el uso de argumentos
adicionales, difciles de imaginar desde el programa principal. En estos casos la solucin
recomendada es utilizar un subprograma no recursivo (que es el que se llama desde el
mdulo principal) encargado de preparar los argumentos adicionales necesarios y, a
continuacin, llamar al subprograma recursivo.
Momentos de realizacin del proceso e implicacin en la modalidad del paso de
argumentos.
La ejecucin de la lgica del algoritmo puede realizarse en cualquiera de las fases del
tratamiento recursivo (ida, transicin o vuelta) o distribuido en varias de ellas.
Recursividad indirecta.
Si tenemos dos subprogramas A y B, se utiliza la recursividad indirecta cuando el
subprograma A contiene una referencia al subprograma B, y a su vez el subprograma B
contiene una referencia al A, como se puede ver en el siguiente esquema:
static void A () {
<sentencia 1>;
<sentencia 2>;
B ();
...
<sentencia n>;
}

37

static void B () {
<sentencia 1>;
...
A ();
......
<sentencia m>;
}

En algunos casos la terminacin anticipada es necesaria para el correcto funcionamiento del programa
mientras que en otros, aunque el programa funcione se debe usar por consideraciones de eficiencia.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 27

1.3.5.4. Ejemplos.
Ejemplo 1. (Utilizacin de un mdulo de llamada).
El programa que se muestra a continuacin utiliza un mtodo que obtiene la suma de
los divisores de un nmero natural. Por ejemplo: la suma de los divisores de 40 (2, 4, 5,
8, 10, 20) es 49.
import java.io.*;
public class Recursividad1 {
static int sumaDivisores (int dato, int presuntoDivisor) {
int resul = 0;
if ((presuntoDivisor*2) <= dato) {
resul = sumaDivisores (dato, presuntoDivisor+1);
if ((dato % presuntoDivisor) == 0)
resul = resul + presuntoDivisor;
}
return resul;
}
static int sumaDivisores (int dato) {
return sumaDivisores (dato, 2);
}
public static void main(String[] args) throws NumberFormatException,IOException{
int d, suma;
BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));
do {
System.out.print ("Introduzca un nmero mayor que cero: ");
d = Integer.parseInt (linea.readLine ());
} while (d <= 0);
suma = sumaDivisores (d);
System.out.print ("La suma de los divisores de " + d + " es " + suma);
}
}

El diseador del programa principal no tiene por qu saber que se necesitan otros
argumentos adicionales al dato (d), sin embargo, el subprograma recursivo realmente
necesita otro argumento (presuntoDivisor, de tipo entero), para probar recursivamente
desde 2 hasta d/2 y controlar la terminacin (pesimista).
Ejemplo 2. (Terminacin anticipada).
El siguiente ejemplo consiste en una funcin booleana (esPrim) que indica si un
nmero (d) que se introduce desde el teclado es primo (true) o no (false).
La condicin de terminacin pesimista es d >

dato

En cada instancia, (fase de ida) se prueba si presuntoDivisor es divisor de dato. En


caso afirmativo dato no es primo y se produce terminacin anticipada con resultado
false, en caso contrario se realiza una llamada recursiva incrementando una unidad el
argumento presuntoDivisor. Si se llega a alcanzar la condicin de terminacin
pesimista significa que el nmero (d) es primo y la funcin devuelve true.

28 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

A continuacin se muestra una posible implementacin.


import java.io.*;
public class Recursividad2 {
static boolean esPrim (int dato, int presuntoDivisor) {
boolean resul;
if ((presuntoDivisor*presuntoDivisor) <= dato)
if ((dato % presuntoDivisor) != 0)
resul = esPrim (dato, presuntoDivisor+1);
else resul = false;
else resul = true;
return resul;
}
static boolean esPrimo (int dato) {
boolean resul;
resul = esPrim (dato, 2);
return resul;
}
public static void main(String[] args) throws NumberFormatException,IOException{
int d;
BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));
do {
System.out.print("Introduzca un nmero mayor que cero: ");
d = Integer.parseInt (linea.readLine());
} while (d <= 0);
if (esPrimo(d))
System.out.println(d + " es primo");
else System.out.println (d + " no es primo");
}
}

Ejemplo 3. (Proceso en la fase de vuelta).


En el siguiente ejemplo se desarrolla un mtodo que devuelve:
o La suma de la primera mitad (los ms pequeos) de los factores primos de un
nmero (d). si el nmero total de factores primos es par. Por ejemplo, el nmero
420 tiene un nmero par (4) de factores primos (2, 3, 5, 7). El mtodo devolver
5 (2+3).
o La suma de la segunda mitad (los ms grandes) de los factores primos de un
nmero (d), si el nmero total de factores primos es impar. Por ejemplo, el
nmero 2310 tiene un nmero impar (5) de factores primos (2. 3, 5, 7, 11). La
funcin devolver 18 (7+11).
Para poder comprobar a la vuelta el nmero de factores primos, sera necesario pasar
la variable que represente el nmero de factores por referencia. Como el valor es entero,
y en Java no es posible pasar tipos primitivos por referencia, crearemos una clase
(Suma), dentro de la cual se utiliza la variable miembro numeroFactores (global dentro
de dicha clase, pero no accesible al resto de mtodos). En la llamada al mtodo
sumaPrimosCondicionada desde el programa principal, es necesario indicar que
pertenece a la clase Suma (haciendo la llamada: Suma.sumaPrimosCondicionada (d)).
A continuacin se muestra el cdigo.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 29

import java.io.*;
public class Recursividad3 {
static boolean esPrim (int dato, int presuntoDivisor) {
boolean resul;
if ((presuntoDivisor*presuntoDivisor) <= dato)
if ((dato % presuntoDivisor) != 0)
resul = esPrim (dato, presuntoDivisor+1);
else resul = false;
else resul = true;
return resul;
}
public static class Suma {
static int numeroFactores = 0; //variable global de la clase Suma
static int sumaPrimos (int dato, int presuntoPrimo) {
int resul, orden;
if ((presuntoPrimo * 2) <= dato)
if ((dato % presuntoPrimo) == 0)
if (esPrim (presuntoPrimo, 2)) {
numeroFactores= numeroFactores + 1;
orden = numeroFactores;
resul = sumaPrimos(dato, presuntoPrimo + 1);
if ((numeroFactores% 2) == 0) {
if (orden <= (numeroFactores / 2))
resul = resul + presuntoPrimo;
}
else {
if (orden > ((numeroFactores / 2) + 1))
resul = resul + presuntoPrimo;
}
}
else resul = sumaPrimos (dato, presuntoPrimo + 1);
else resul = sumaPrimos (dato, presuntoPrimo + 1);
else resul = 0;
return resul;
}
static int sumaPrimosCondicionada (int dato) {
int resul;
resul = sumaPrimos (dato, 2);
return resul;
}
}
public static void main(String[] args) throws NumberFormatException,IOException{
int d, suma;
BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));
do {
System.out.print("Introduzca un nmero mayor que cero: ");
d = Integer.parseInt (linea.readLine());
} while (d <= 0);
suma = Suma.sumaPrimosCondicionada (d);
System.out.println ("El resultado es: " + suma);
}
}

En este caso, el proceso no puede realizarse completamente durante la fase de


ida, dado que hasta la transicin no se conocer el nmero total de factores

30 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

primos, por lo que no se sabe qu hay que sumar. La parte de proceso que se realiza
en ella consiste en:
o Identificar si un nmero (argumento presuntoPrimo, pasado por valor) es
primo y factor de d.
En caso negativo se realiza una nueva llamada recursiva con
presuntoPrimo+1.
En caso afirmativo:
Se contabiliza en la variable numeroFactores global dentro de la clase
Suma, inicializado a 0 (pues se utilizar su valor final en la fase de
vuelta).
Se toma nota del nmero de orden (orden) del factor primo
encontrado (variable local que recoge el valor actual de
numeroFactores)38.
NOTA IMPORTANTE: Evitar el error de intentar pasar a la siguiente instancia el valor
de una variable local.

El proceso de la fase de vuelta est condicionado por el lugar desde el que se


hizo la llamada recursiva:
o Si se hizo en una instancia correspondiente a un valor de presuntoPrimo que
no era factor primo, simplemente se retorna el valor recibido desde la
instancia siguiente hacia la anterior39.
o

En caso contrario:
Se recibe el valor devuelto por el mtodo desde la instancia siguiente
(resul).
Si el valor de orden se encuentra dentro del rango adecuado (primeros o
ltimos, segn proceda) se suma al valor de resul el de presuntoPrimo.
Se devuelve el resultado a la instancia anterior.

38

En las instancias correspondientes a valores de presuntoPrimo que no son factores primos el valor de la
variable orden es aleatorio
39
Vamos marcha atrs.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 31

Ejemplo 4. (Recursividad indirecta).


En el siguiente ejemplo, se desarrollan los mtodos esPar y esImpar que utilizan la
recursividad indirecta para indicar si el nmero que se recoge del teclado es par o
impar.
import java.io.*;
public class RecursividadIndirecta {
static boolean esPar (int n) {
boolean resul = true;
if (n != 0)
resul = esImpar (n - 1);
return resul;
}
static boolean esImpar (int n) {
boolean resul = false;
if (n != 0)
resul = esPar (n - 1);
return resul;
}
public static void main (String [ ] args) throws IOException {
int n;
boolean esPar;
BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));
System.out.print ("Escriba un nmero para saber si es par o impar: ");
n = Integer.parseInt (linea.readLine ());
esPar = esPar (n);
if (esPar)
System.out.println ("El nmero " + n + " es par");
else System.out.println ("El nmero " + n + " es impar");
}

32 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

1.4. ESTRUCTURAS DE DATOS.


Una estructura de datos es una agrupacin de stos que se trata como una unidad en
su conjunto. Se construyen a partir de los tipos de datos simples, vistos en la seccin
1.2.3.1.
Las estructuras de datos pueden ser homogneas (todos los datos son del mismo tipo)
o heterogneas (constituidas por datos de tipos diferentes).
Las estructuras de datos homogneas ms representativas son los vectores, las tablas
y, en general, las matrices n-dimensionales. El ejemplo ms representativo de estructuras
de datos heterogneas son los registros
Desde otro punto de vista puede hablarse de estructuras de datos estticas y
dinmicas.
Una estructura esttica se caracteriza porque su tamao es conocido a priori (antes de
la ejecucin del programa). En consecuencia, en el cdigo del programa se declaran
variables de tipo esttico y en la compilacin se reserva en memoria el espacio necesario
para ellas.
Por el contrario, las estructuras de datos dinmicas no tienen un tamao predefinido
y la memoria utilizada para almacenarlas se reserva o libera, en tiempo de ejecucin,
segn se requiera.
1.4.1. Estructuras de datos dinmicas
Se dice que una estructura de datos es dinmica cuando inicialmente (en el momento
de la compilacin) no tiene espacio asignado para almacenar informacin. Durante la
ejecucin del programa el sistema (en tiempo de ejecucin, run time) asigna y libera
espacio en memoria, en funcin de las necesidades.
Los temas 3 (Listas), 4 (rboles) y 5 (Grafos) describen distintos tipos de Estructuras
de Datos Dinmicas.
En algunos lenguajes de programacin, para permitir la implementacin de
estructuras de datos dinmicas es necesario un tipo de datos especial, denominado puntero
(pointer).
El concepto de puntero (pointer) hace referencia a una variable cuyo contenido es la
direccin de otra variable (nodo) que realmente contiene el propio dato que se emplea en el
programa40.

40

Este concepto toma su suporte fsico en el mecanismo de direccionamiento indirecto tal como se maneja en
los lenguajes de bajo nivel.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 33

La figura 1.6. muestra un modelo de funcionamiento.

Variable de trabajo
(apuntada por puntero)

puntero

Memoria
dinmica

Memoria
esttica

Figura 1.6. Punteros y variables de trabajo.


El inters del uso de punteros en lenguajes de programacin de alto nivel reside en
que constituyen la base para la generacin de estructuras de datos dinmicas. Esto es, que a
lo largo de la ejecucin de un programa, y como consecuencia de las sucesivas operaciones
de insercin y eliminacin, el sistema en tiempo de ejecucin (run time) reserva y libera
respectivamente unidades (nodos) de una regin de memoria destinada a uso dinmico (a
diferencia de la que se reserva en el momento de la compilacin que contiene el cdigo
mquina y el espacio requerido por las variables estticas). Este espacio (esttico) se
mantiene inalterado durante toda la ejecucin del programa.
La utilizacin de esta posibilidad requiere que el lenguaje de programacin disponga
de operaciones que soliciten (de forma transparente al programador) espacio en la memoria
dinmica para crear nuevos nodos, as como la contraria, para liberar espacio
correspondiente a nodos que ya no se necesitan y poder disponer, en consecuencia, de dicho
espacio para uso posterior. En Java se utiliza la sintaxis:
<tipo> <variable> = new <tipo>;

para crear un nodo

La variable puntero (en Java se denominan referencias), se almacena en la memoria


esttica y su tamao es fijo. No as los nodos que pueden ser de diferente naturaleza. Es
decir que en la declaracin de un nodo debe hacerse referencia al tipo de dato al que apunta
para que cuando se cree un nodo se reserve con el tamao necesario. Por ejemplo, al
ejecutar el cdigo en Java:
char [ ] puntVector = new char [100];

se declara una variable (puntVector) de tal forma que, al realizar la operacin new se
reserve el espacio necesario para almacenar un vector de 100 caracteres.
Adems de la operacin ya indicada de reserva (new), las referencias admiten las
siguientes operaciones:

34 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Asignacin: <puntero1> = <puntero2>. La referencia <puntero2> se copia en


<puntero1>, o dicho en otros trminos: <puntero1> deja de apuntar al nodo a que
apuntaba previamente para pasar a apuntar al nodo apuntado por <puntero2>. Una
vez ejecutada la operacin ambos punteros apuntan al mismo nodo41.

Comparacin: <puntero1> == <puntero2>. Devuelve un valor booleano en funcin


de que ambas referencias sean iguales o no, o dicho en otros trminos: que apunten
o no al mismo nodo.

Aunque, como se ha indicado, una variable de tipo puntero apunta a una zona de
memoria (nodo) existe una situacin excepcional consistente en no apuntar a ninguno. En
Java se utiliza para esto la constante null.
Sobre las variables referencia, se realizan las operaciones propias del tipo de datos a
que pertenezcan.

41

Este tipo de operaciones deber hacerse con especial cuidado pues, si no se han tomado previamente las
precauciones oportunas, podra perderse la informacin almacenada en el nodo inicialmente apuntado por
<puntero1> (se perder si no tenemos otra referencia a esa informacin)

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 35

1.4.2. Estructuras de datos estticas.


Una estructura esttica se caracteriza porque su tamao es conocido a priori (antes de
la ejecucin del programa). En consecuencia, en el cdigo del programa se declaran
variables de tipo esttico y en la compilacin se reserva en memoria el espacio necesario
para ellas.
1.4.2.1. Estructuras de datos homogneas.
En las estructuras de datos homogneas todos los datos son del mismo tipo. Como
ejemplo veremos los vectores, las tablas y, en general, las matrices n-dimensionales.
1.4.2.1.1. Unidimensionales (vectores).
El caso ms sencillo es el vector, que se define como un conjunto de elementos, cada
uno de los cuales puede identificarse mediante su posicin relativa (ndice42). La figura
muestra un ejemplo de vector de 5 nmeros enteros.
0

20

40

60

80

100

Tabla 1.6. Vector de nmeros enteros.


El lenguaje Java permite utilizar este tipo de estructuras utilizando la sintaxis43:

Declaracin de variables de tipo vector:


<tipo de datos de los elementos del vector> [] <nombre de la variable>;

Por ejemplo, la lnea:

int [] vector1;

Declara una variable llamada vector1 que es un vector de enteros inicialmente vaco.
Para indicar el tamao del vector, se puede hacer directamente en la propia lnea de
declaracin:
Int [] vector1 = new int [5];

//declara un vector de 5 elementos de tipo entero.

Acceso:
o Al conjunto. Por ejemplo asignar un vector a otro del mismo tipo:
vector2 = vector1.

En realidad esta instruccin no copia el contenido de vector1 en vector2, sino que


hace que vector2 apunte a la misma posicin de memoria en la que est el vector1.
o A un elemento del vector:
<variable_tipo_vector> [<ndice>];

42

En Java el ndice es numrico y siempre comienza en 0.


Se describen solamente las operaciones bsicas. El manejo de este tipo de estructuras admite una gran
flexibilidad. Consultar el manual del lenguaje para mayor informacin.
43

36 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Con lo que se podr realizar cualquier operacin acorde con el tipo del elemento
correspondiente. Por ejemplo, si datos es un vector de nmeros enteros podramos hacer:
datos [3] = datos [3] * 2;

Ejemplo.
A continuacin se retoma el ejemplo expuesto en el apartado 1.3.4.1 (valores medio y
rango de un conjunto de 5 nmeros enteros) utilizando un vector.
import java.io.*;
public class PruebaVectores {
static float media (int [ ] dato) {
float resul = 0;
int i;
for (i = 0; i < 5; i ++)
resul = resul + dato [i];
resul = resul / i;
return resul;
}
static int max (int [] dato) {
int resul,i;
resul = dato[0];
for (i = 1; i<5; i++)
if (dato[i] > resul)
resul = dato[i];
return resul;
}
static int min (int [] dato) {
int resul,i;
resul = dato[0];
for (i = 1; i < 5; i ++)
if (dato [i] < resul)
resul = dato [i];
return resul;
}
public static void main(String [] args)throws NumberFormatException,IOException{
int [ ] d = new int [5];
int i, rango;
float media;
BufferedReader linea = new BufferedReader(new InputStreamReader(System.in));
System.out.println ("Introduce cinco nmeros enteros: ");
for (i = 0; i < 5; i ++)
d[i] = Integer.parseInt (linea.readLine ());
media = media (d);
System.out.println ("El valor medio es: " + media);
rango = max (d) - min (d);
System.out.println ("y el rango: " + rango);
}
}

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 37

1.4.2.1.2. Bidimensionales (matrices).


Una matriz es una estructura de dos dimensiones: horizontal (filas) y vertical
(columnas), que contiene elementos del mismo tipo. Se puede hacer referencia a cada uno
de los elementos (celdas) mediante un ndice de fila y otro de columna.
Por ejemplo, la tabla siguiente (temperaturas) muestra las temperaturas mxima y
mnima a lo largo de una semana (las filas indican el da de la semana y las columnas las
temperaturas mnima y mxima, respectivamente. Por ejemplo, la temperatura mnima del
4 da es: temperaturas (3, 0) = 5 grados).
temperaturas

7
8
6
5
7
6
5

15
17
13
14
14
16
13

1
2
3
4
5
6

Tabla 1.7. Temperaturas a lo largo de una semana


Los lenguajes de programacin permiten utilizar este tipo de estructuras. Para sto en
Java se emplea la siguiente sintaxis:
Declaracin de variables de tipo matriz:
< tipo de datos de los elementos > [ ] [ ] <nombre de la variable>;

Ejemplo: int [ ] [ ] temperaturas;


Acceso:
o Al conjunto. Por ejemplo asignar una matriz a otra del mismo tipo:
matriz2 = matriz1.

Como en el caso de los vectores, esta instruccin no copia el contenido de matriz1 en


matriz2, sino que hace que matriz2 apunte a la misma posicin de memoria que matriz1.
o A un elemento de la matriz:
<variable_tipo_matriz>[<ndice1>] [<ndice2>];

(Con lo que se podr realizar cualquier operacin acorde con el tipo del elemento
correspondiente). Por ejemplo: temperaturas [3] [1] = temperaturas [3] [1] + 2;
Ejemplo.
El siguiente cdigo permite encontrar la temperatura mnima de la semana y el da en
que se produjo la mxima diferencia (en caso de coincidencia de varias se muestra el
primero de ellos). En el ejemplo anterior los resultados seran:
Temperatura mnima: 5 grados.
Da de mxima diferencia de temperaturas: 5 (10 grados).

38 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

import java.io.*;
public class PruebaMatrices {
static BufferedReader linea=new BufferedReader(new
InputStreamReader(System.in));
public static void leerMatriz (int [][] temperaturas) throws
NumberFormatException, IOException{
int i,j;
for (i = 0;i < 7; i ++)
for (j = 0; j < 2; j ++) {
System.out.println ("Valor dia: " + i + " extremo: " + j + ": ");
temperaturas [i] [j] = Integer.parseInt (linea.readLine ());
}
}
public static int min (int [][] temperaturas) {
int resul, i;
resul = temperaturas [0] [0];
for (i = 1; i < 7; i++)
if (temperaturas [i] [0] < resul)
resul = temperaturas [i] [0];
return resul;
}
public static int maxDif (int [] [] temperaturas) {
int resul, i, dif;
resul = 0;
dif = temperaturas [0][1]- temperaturas [0][0];
for (i = 1; i < 7; i++) {
if (temperaturas [i][1] - temperaturas [i][0] > dif) {
dif = temperaturas [i][1] - temperaturas [i][0];
resul = i;
}
}
return resul;
}
public static void main(String[] args) throws NumberFormatException,
IOException{
int [][] temperaturas = new int [7][2];
int minimaTemperatura, diferenciaTemperaturas;
leerMatriz (temperaturas);
minimaTemperatura = min (temperaturas);
diferenciaTemperaturas = maxDif (temperaturas);
System.out.println ("Resultados:");
System.out.println ("Temperatura minima: " + minimaTemperatura);
System.out.println ("Dia extremo: " + diferenciaTemperaturas);
}
}

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 39

1.4.2.1.3. N-dimensionales.
Por extensin, lo explicado para una y dos dimensiones se puede aplicar al caso de
matrices N-dimensionales.
1.4.2.2. Estructuras de datos heterogneas.
Como ya se ha indicado, estn constituidas por un conjunto de tipos de datos (ya sean
datos simples u otras estructuras de datos) de diferente naturaleza. La forma bsica de
estructura de datos heterognea es el registro cuyos elementos se llaman campos (o
atributos)44.
Por ejemplo, la figura siguiente muestra un registro (alumno) cuyos campos son: el
nmero de matrcula (numeroMatricula), apellidos (apellidos), nombre (nombre), direccin
de correo electrnico (eMail), ao de nacimiento (anio) y calificacin (calificacion).

alumno

numeroMatricula

apellidos

nombre

eMail

ao

calificacion

bc2658

Snchez Arellano

Estela

esanchez@servidor.es

1987

6.75

Tabla 1.8. Registro alumno


Para manejar este tipo de estructuras en Java45, se construye una clase dentro de la
cual se definen los datos que van a formar parte de la estructura, as como uno o varios
constructores, como se puede ver en el siguiente ejemplo (correspondiente a la figura):
class RegistroAlumno {
public
public
public
public
public
public

String numeroMatricula;
String apellidos;
String nombre;
String eMail;
int ao;
float calificacion;

public RegistroAlumno (){


numeroMatricula= null;
apellidos = null;
nombre = null;
eMail= null;
ao = 1980;
calificacion = 0;
}

44

Con frecuencia uno de los campos (o combinacin de ellos) se utiliza como identificativo del registro. A
dicho campo (o conjunto) se le denomina clave (key).
45
Existen otras operaciones. Consultar el manual del lenguaje.

40 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Acceso:
o Al conjunto. Por ejemplo apuntar con una variable de tipo registro mismo sitio
que apunta otra del mismo tipo:
variable2_RegistroAlumno = variable1_RegistroAlumno;

o A un campo del registro:


<variable>.<campo>;

Con lo que se podr realizar cualquier operacin acorde con el tipo del
elemento correspondiente. Por ejemplo: alumno.eMail = esanchez@servidor.es;
Normalmente, el trabajo con un registro (o con un conjunto pequeo de variables
independientes de tipo registro) ofrece poco juego. Lo habitual es utilizar una coleccin
de registros estructurados en un vector, o mucho mejor, almacenarlos, como un fichero
en un dispositivo de almacenamiento externo (tpicamente disco).
La utilizacin de ficheros en dispositivos de almacenamiento externos se explica en el
apartado 1.5. A continuacin se muestra un ejemplo que utiliza un vector de registros del
tipo anterior cuyo modelo se ilustra grficamente en la figura siguiente:
0
1
2
3
4
5

numeroMatricula

apellidos

nombre

eMail

ao

calificacion

aa1253
ax0074
mj7726
lp1523
bc2658
gb1305

Arias Gonzlez
Garca Sacedn
Lpez Medina
Ramrez Heredia
Snchez Arellano
Yuste Pelez

Felipe
Manuel
Margarita
Jorge
Estela
Juan

farias@servidor.es
mgarcia@servidor.es
mlopez@servidor.es
jramirez@servidor.es
esanchez@servidor.es
jyuste@servidor.es

1988
1985
1990
1998
1989
1990

3.50
8.35
7,70
4,50
6.75
5,50

Tabla 1.9. Vector de registros.


El programa que se muestra a continuacin se ha incluido en dos ficheros (clases) que
forman parte del mismo paquete (package):
RegistroAlumno, que contiene el constructor del registro (construye un registro vaco),
as como los mtodos aCadena, que devuelve el contenido del registro en un String, y
cargarRegistro, que introduce los datos recogidos por teclado en un registro.
PruebaRegistro, en el que aparece el programa principal e incluye dos mtodos,
cargarTabla, que permite cargar la estructura en la memoria central y mediaCalif, que
utiliza la estructura anterior para calcular la calificacin media.
Primero se muestra la clase RegistroAlumno:

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 41

Import java.io.*;
class RegistroAlumno {
public RegistroAlumno () {
numeroMatricula= null;
apellidos = null;
nombre = null;
eMail= null;
ao = 1980;
calificacion = 0;
}
public String aCadena () {
return numeroMatricula + " " + apellidos + " " + nombre + " " + eMail +
" " + ao + " " + calificacion;
}
public String numeroMatricula;
public String apellidos;
public String nombre;
public String eMail;
public int ao;
public float calificacion;
public void cargarRegistro () throws IOException {
BufferedReader linea = new BufferedReader (new InputStreamReader (System.in));
System.out.println ("Numero de matricula: ");
numeroMatricula = new String (linea.readLine ());
System.out.println ("Apellidos: ");
apellidos = new String (linea.readLine ());
System.out.println ("Nombre: ");
nombre = new String (linea.readLine ());
System.out.println ("Correo electronico: ");
eMail = new String (linea.readLine ());
System.out.println ("Ao de nacimiento: ");
ao = Integer.parseInt (linea.readLine());
System.out.println ("Calificacin: ");
calificacion = Float.parseFloat (linea.readLine());
System.out.println (this.aCadena ());
}
}

Como se ha visto en el apartado 1.2.4., los mtodos aCadena y cargarRegistro son


mtodos de objeto (sin modificador static), y para utilizarlos desde fuera de la clase tendr
que hacerse de la forma <nombreVariable>.<nombreMtodo>. Para poder utilizar los
mtodos de objeto, es necesario que previamente hayamos utilizado el constructor del
objeto sobre la variable correspondiente, como hacemos con las instrucciones:
for (i = 0; i < 6;i++)
alumnos [i]= new RegistroAlumno ();

A continuacin aparece la clase PruebaRegistro, donde se incluye el programa


principal:

42 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

import java.io.*;
public class PruebaRegistro {
static void cargarTabla (RegistroAlumno [ ] alumnos) throws IOException {
int i;
for (i = 0; i < 6; i++) {
System.out.println ("Datos del alumno N: "+ i);
alumnos [i].cargarRegistro ();
}
}
static float mediaCalif (RegistroAlumno [ ] alumnos) {
float resul = 0;
int i;
for (i = 0; i < 6; i++) {
System.out.println(alumnos [i].aCadena ());
resul = resul + alumnos [i].calificacion;
}
return resul/6;
}
public static void main (String [ ] args) throws IOException {
RegistroAlumno [ ] alumnos = new RegistroAlumno [6];
float media;
int i;
for (i = 0; i < 6; i++)
alumnos [i]= new RegistroAlumno ();
cargarTabla (alumnos);
media = mediaCalif (alumnos);
System.out.println ("La media de las calificaciones es: " + media);
}
}

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 43

1.5. UTILIZACIN DE DISPOSITIVOS DE ALMACENAMIENTO


EXTERNO.
Con frecuencia las aplicaciones informticas trabajan sobre datos almacenados de
forma estable en dispositivos de almacenamiento (tpicamente discos magnticos).
Una primera vez el usuario crea un fichero (file) y lo guarda en un disco.
Posteriormente podr utilizar dicho fichero, consultar y modificar los datos, as como
aadir nuevos o eliminar alguno/s de los actuales.
La utilizacin de ficheros en disco es muy amplia y va ms all de los objetivos de
este curso. Simplemente indicar que los ficheros estn constituidos por un conjunto de
registros que definen las caractersticas de cada uno de los elementos (fichas) del
fichero46.
Otro aspecto a considerar es cmo organizar las fichas dentro del dispositivo
(fichero). Con soportes tradicionales la forma de organizacin es nica (por ejemplo,
alfabtica) y secuencial (una ficha tras otra, en el orden predefinido). La Informtica ha
heredado esta idea aunque permite otras modalidades de organizacin y modos de acceso
adicionales a los puramente nicos y secuenciales.
1.5.1. Ficheros de texto.
En un fichero de texto, cada una de sus fichas consiste en una cadena de caracteres
de longitud variable (lneas de texto). Las fichas se almacenan una tras otra sin mantener
ningn criterio de orden entre s. Por ejemplo (marsell.txt):
Allons enfants de la Patrie,
Le jour de gloire est arriv!
Contre nous de la tyrannie,
L'tendard sanglant est lev,(bis)
Entendez-vous dans le campagnes,
Mugir ces froces soldats?
Ils viennent jusque dans nos bras,
gorger nos fils, nos compagnes!

Entre cada una de las lneas existen cdigos invisibles cuyo efecto es hacer saltar al
inicio de la lnea siguiente. Estos cdigos se generan automticamente al pulsar la tecla
Intro. As mismo, para indicar el final del fichero de texto, el usuario deber haber
pulsado la tecla de funcin F6.
La figura siguiente muestra el cdigo ASCII correspondiente al ejemplo.

46

Este concepto est heredado de los antiguos ficheros (o archivadores) que contenan fichas (de papel)
cada una de las cuales representaba una informacin unitaria: ficheros de pelculas, personas, asignaturas.

44 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Pareja de cdigos (ocultos):


Salto de lnea (LF): X=0A; dec=10
Retorno de carro (CR): X=0D;
dec=13

Figura 1.7. Cdigos de fin de lnea y retorno de carro en un fichero de texto.


La figura siguiente muestra, a ttulo de ejemplo, un programa que crea un fichero
(archivo.txt)47 e introduce en l dos lneas de texto (procedentes del teclado y recogidas,
sucesivamente, en la variable, de tipo String, lineaLeida). Una vez finalizado el proceso el
fichero deber cerrarse para permitir posteriores usos del mismo.

import java.io.*;
public class prueba_fichero_texto {
lineaLeida

Marse.txt

public static void main(String[] args) throws IOException {


FileWriter fich_s = new FileWriter ("archivo.txt");
BufferedWriter bw = new BufferedWriter (fich_s);
PrintWriter salida = new PrintWriter (bw);
BufferedReader linea = new BufferedReader (new
InputStreamReader(System.in));
String lineaLeida;
System.out.println ("Escriba la primera linea: ");
lineaLeida = new String (linea.readLine());
salida.println (lineaLeida);
System.out.println ("Escriba la segunda linea: ");
lineaLeida = new String (linea.readLine());
salida.println (lineaLeida);
salida.close();
}
}

Figura 1.8. Funcionamiento del programa

47

La ruta, nombre y extensin del fichero se eligen libremente (con las restricciones propias del sistema
operativo). En el ejemplo se han utilizado:
Ruta: (por omisin) la misma que el programa.
Nombre: archivo.
Extensin txt (para poder visualizarlo mediante el bloc de notas notepad- de Windows).

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 45

import java.io.*;
public class PruebaFicheroSalida {
public static void main (String [ ] args) throws IOException {
FileWriter fich_s = new FileWriter ("archivo.txt");
BufferedWriter bw = new BufferedWriter (fich_s);
PrintWriter salida = new PrintWriter (bw);
BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));
String lineaLeida;
System.out.println ("Escriba la primera
lineaLeida = new String (linea.readLine
salida.println (lineaLeida);
System.out.println ("Escriba la segunda
lineaLeida = new String (linea.readLine
salida.println (lineaLeida);
salida.close ();

linea: ");
());
linea: ");
());

}
}

La figura siguiente ilustra un ejemplo de posible resultado de la ejecucin del


programa anterior.

Figura 1.9. Resultado de la ejecucin del programa

46 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

1.5.1.1. Principales consideraciones semnticas.


El programador no es consciente del fichero fsico con que est trabajando. Se crea
un nombre simblico (por ejemplo fich_s), y sobre l se realizan todas las
operaciones48. Slo existe una excepcin, la sentencia:
FileWriter fich_s = new FileWriter ("archivo.txt");

establece una vinculacin entre el nombre fsico del fichero y el nombre lgico.
Se utiliza una variable de tipo String (en el ejemplo: lineaLeida) como puente
entre la memoria del ordenador y el dispositivo de almacenamiento (disco). Segn
sea el sentido de la transferencia se puede hablar de lectura: disco memoria
(readline) o de escritura memoria disco (println).
Hay que preparar el fichero en disco para utilizarlo (apertura) y dejarlo disponible
para posteriores usos (cierre).
1.5.1.2. Sintaxis de las principales operaciones relacionadas con los ficheros de texto49.
Acceso (Asignacin y apertura) y declaracin de variables:
o Crear un nuevo fichero y destruir, en su caso, un fichero previo.
Posteriormente se podr escribir en l:
FileWriter <nombre lgico> = new FileWriter (<nombre fsico>);
BufferedWriter <buffer escritura> = new BufferedWriter (<nombre lgico>);
PrintWriter <variable> = new PrintWriter (<buffer escritura>);

o Preparar un fichero (que debe existir previamente) para poder leer


posteriormente su contenido:
FileReader <nombre lgico> = new FileReader (<nombre fsico>);
BufferedReader <buffer lectura> = new BufferedReader (<nombre lgico>);

o Escribir en un fichero (previamente creado) nuevas lneas de texto a partir de


la ltima:
FileWriter <nombre lgico> = new FileWriter (<nombre fsico>,true);

Proceso:
o Leer (transferir a la variable correspondiente) el contenido de una lnea del
fichero y prepararse para la siguiente.
<variable tipo String> = <buffer lectura>.readline();

o Escribir (transferir) el contenido de una lnea (<expresin tipo String>) al


fichero y prepararse para la siguiente:
<variable>.println = <expresin tipo String>;

48

Dicho fichero estar gestionado por el sistema operativo. Con frecuencia se cambia de ubicacin los
ficheros (fsicos) y de no seguir esta filosofa, esto implicara re-escribir parte del cdigo cada vez que un
fichero cambiase de ubicacin.
49
Para mayor informacin consultar el manual de programacin.

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 47

Terminacin:
o Dejar el fichero preparado para usos posteriores:
<variable>.close ();

Por ejemplo, si queremos leer un fichero hasta llegar al final lo podemos hacer
utilizando lo siguiente:
import java.io.*;
public class PruebaFicheroEntrada {
public static void main (String [ ] args) throws IOException {
String lineaLeida;
FileReader fichLeido = new FileReader ("archivo.txt");
BufferedReader entrada = new BufferedReader (fichLeido);
System.out.println ("Contenido del fichero: ");
while ((lineaLeida = entrada.readLine ()) != null)
System.out.println (lineaLeida);
entrada.close ();
}
}

1.5.2. Ficheros binarios.


En un fichero binario, las fichas son elementos de tipo registro50. En consecuencia
(entre otras) no se puede visualizar su contenido con un visor de texto del sistema operativo
(por ejemplo, el bloc de notas de Windows).
Para poder guardar objetos (por ejemplo, del tipo RegistroAlumno visto en apartados
anteriores) en un fichero hay que hacerlos serializables. Mediante la serializacin, un
objeto se convierte en una secuencia de bytes con la que se puede reconstruir
posteriormente manteniendo el valor de sus variables. Esto permite guardar un objeto en un
archivo o mandarlo por red. Una clase se serializa aadiendo en su definicin:
implements Serializable

Para poder leer y escribir objetos que se han declarado como serializables se utilizan
las clases ObjectInputStream y ObjectOutputStream, que cuentan con los mtodos
writeObject() y readObject().
Para escribir un objeto en un fichero se utilizar:
ObjectOutputStream <objEscrito> = new ObjectOutputStream (new
FileOutputStream("<nombre fichero>"));
<objEscrito>.writeObject (<variable>);

50

Ver apartado 1.4.2 Estructuras de datos heterogneas.

48 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

Mientras que las instrucciones utilizadas para leerlos despus seran:


ObjectInputStream <objLeido> = new ObjectInputStream (new FileInputStream ("<nombre
fichero>"));
<tipo> <variable> = (<tipo>) <objLeido>.readObject ();

Cuando se ha terminado de leer o escribir el fichero correspondiente es necesario


cerrar el fichero con:
<nombre fichero>.close()

El siguiente ejemplo es una variante del programa de gestin de alumnos visto en el


apartado 1.4.2.2. Sus caractersticas ms importantes son51:
Concepcin modular: un programa principal y un conjunto de subprogramas.
No se utilizan variables globales.
El programa principal:
o Prepara el fichero en disco:
String nombre;
BufferedReader linea = new BufferedReader (new
InputStreamReader(System.in));
System.out.println ("Introducir nombre del fichero: ");
nombre = new String (linea.readLine());

o Su lgica se basa en un planteamiento de tipo men:

Llama al mdulo que realiza la interfaz de usuario: menu (sin


argumentos)

Recibe (variable op) la opcin seleccionada, la analiza (mediante una


estructura case) e invoca al mdulo correspondiente:
crearFichero (fichero).
cargarTabla (alumnos, fichero)
mediaCalif (alumnos)

o Utiliza el menor conjunto posible de informacin (variables). Obsrvese que


el programa principal no necesita para nada utilizar registros
correspondientes a alumnos individuales (tipo RegistroAlumno).
El procedimiento menu. Es autnomo (no necesita recibir ni devolver argumentos).
La funcin mediaCalif (alumnos). El nico argumento que necesita es una tabla de
registros de alumnos (RegistroAlumno []).
El procedimiento cargarTabla (alumnos, fichero). Utiliza como informacin de
entrada un fichero y genera en memoria (alumnos) una estructura RegistroAlumno
51

Por simplicidad no se han considerado situaciones excepcionales (por excepcin). Por ejemplo:
No tiene sentido utilizar la opcin [2]: Cargar tabla de registros, si no existe el fichero en disco.
Tampoco lo tiene utilizar la opcin [3]: Calcular calificacin media, si no se ha ejecutado (con xito)
previamente la opcin [2].

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 49

[] que quedar a disposicin del programa principal. Se encarga de abrir, en modo


lectura, el fichero y cerrarlo al finalizar.
El procedimiento crearFichero (fichero). Genera un fichero binario en disco a partir
de los datos introducidos por el usuario desde el teclado. Abre el fichero en modo
escritura y lo cierra al terminar.
El siguiente esquema muestra grficamente la arquitectura de la aplicacin.

Inicio

menu

Mdulo
principal

Fin

crearFichero

cargarTabla

op

mediaCalif

Figura 1.10. Arquitectura del programa ejemplo.


El cdigo del ejemplo sera:
import java.io.*;
public class PruebaFichES {
static void crearFichero (RegistroAlumno [ ] alumnos, String nombref) throws
IOException {
int i;
FileOutputStream fich = new FileOutputStream (nombref);
ObjectOutputStream objEscrito = new ObjectOutputStream (fich);
for (i = 0; i < 6; i++) {
alumnos [i] = new RegistroAlumno();
System.out.println ("Datos del alumno N: "+ i);
alumnos [i].cargarRegistro ();
objEscrito.writeObject (alumnos [i]);
}
fich.close ();
}
static void cargarTabla (RegistroAlumno [ ] alumnos, String nombref) throws
IOException, ClassNotFoundException {
int i;
FileInputStream fich =new FileInputStream (nombref);
ObjectInputStream objLeido = new ObjectInputStream (fich);
for (i = 0; i < 6; i++) {
alumnos [i] = (RegistroAlumno) objLeido.readObject ();
System.out.println ("Datos del alumno N: " + i);
System.out.println(alumnos [i].aCadena ());
}
fich.close ();
}

50 INTRODUCCIN A LA PROGRAMACIN

ESTRUCTURAS DE DATOS

static float mediaCalif (RegistroAlumno [ ] alumnos) {


float resul;
int i;
resul = 0;
for (i = 0; i < 6; i++) {
System.out.println (alumnos [i].aCadena ());
resul = resul + alumnos [i].calificacion;}
return resul/6;
}
static void menu () {
System.out.println
System.out.println
System.out.println
System.out.println
System.out.println
System.out.println
}

("OPCIONES:");
("Opcion 1: Crear fichero.");
("Opcion 2: Cargar tabla de registros.");
("Opcion 3: Calcular calificacion media.");
("Opcion 0: Salir.");
("\n Introduzca opcion: ");

public static void main (String[] args) throws IOException,


ClassNotFoundException {
RegistroAlumno [ ] alumnos = new RegistroAlumno [6];
float media;
int op;
String nombre;
BufferedReader linea = new BufferedReader (new InputStreamReader(System.in));
System.out.println ("Introducir nombre del fichero: ");
nombre = new String (linea.readLine ());
menu();
op = Integer.parseInt (linea.readLine ());
while (op != 0) {
switch (op) {
case 1: crearFichero (alumnos,nombre);
break;
case 2: cargarTabla (alumnos, nombre);
break;
case 3: media = mediaCalif (alumnos);
System.out.println ("La media de las calificaciones es: "+media);
break;
default: System.out.println("opcin no valida");
break;
}
menu ();
op = Integer.parseInt (linea.readLine ());
}
System.out.println ("Adios");
}
}

ESTRUCTURAS DE DATOS

INTRODUCCIN A LA PROGRAMACIN 51

TEMA 1. ....................................................................................................................... 1
1.1.

Conceptos bsicos. ......................................................................................... 1

1.1.1.

Caractersticas especficas del lenguaje Java. ........................................ 3

1.1.2.

Entorno de programacin (IDE). ............................................................ 4

1.2.

Sintaxis del lenguaje Java............................................................................... 5

1.2.1.

Estructura de un programa en Java......................................................... 5

1.2.2.

Nomenclatura habitual en Java............................................................... 6

1.2.3.

Los datos................................................................................................. 6

1.2.4.

Sentencias. ............................................................................................ 10

1.3.

Programacin modular. Subprogramas. ....................................................... 18

1.3.1.

Reflexin previa. .................................................................................. 18

1.3.2.

Tecnologa para la programacin modular. .......................................... 18

1.3.3.

Mecanismos para el paso de informacin. ........................................... 20

1.3.4.

Aplicacin al lenguaje Java. ................................................................. 21

1.3.5.

Recursividad. ........................................................................................ 24

1.4.

Estructuras de datos. ..................................................................................... 32

1.4.1.

Estructuras de datos dinmicas............................................................. 32

1.4.2.

Estructuras de datos estticas. .............................................................. 35

1.5.

Utilizacin de dispositivos de almacenamiento externo. ............................. 43

1.5.1.

Ficheros de texto. ................................................................................. 43

1.5.2.

Ficheros binarios. ................................................................................. 47

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 51

TEMA 2

Tipos Abstractos de Datos.


2.1. CONCEPTO
Como se vio en el apartado 1.4, se entiende por Estructura de Datos una agrupacin
de datos, simples o compuestos, del mismo o diferente tipo, que constituyen una entidad en
su conjunto (por ejemplo un vector o un registro).
Evolucionando en el concepto de estructura de datos aparece el de Tipo Abstracto de
Datos (TAD) que obedece a la tcnica de abstraccin de datos y sienta las bases para una
adecuada compresin de la Programacin Orientada a Objetos.
Una definicin de TAD u Objeto sera: El conjunto constituido por la estructura de
datos y las operaciones asociadas a la misma que permite modelar el comportamiento de
una entidad real.
Se trata de construir entidades (TADs, Objetos, etc.) que puedan ser posteriormente
utilizadas por otros.
Sus principales caractersticas son:
Ocultamiento. El TAD tiene un comportamiento de caja negra dado que quien lo
usa sabe qu puede hacer pero no cmo lo hace.

52 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

Encapsulamiento (y, en consecuencia, Proteccin). El usuario de TADs no tiene


acceso y, por tanto, no puede modificar sus caractersticas. No obstante, puede partir de
l para construir otros TADs1
Compilacin separada: El resultado de la compilacin del TAD se pone a disposicin
de los usuarios en forma de unidades que pueden utilizarse como si estuvieran
predefinidas en el lenguaje de programacin.
El objetivo de emplear este tipo de mecanismos obedece a criterios de productividad
en la Ingeniera del Software. De forma simplificada se trata de clasificar a los
profesionales de la construccin de Software en dos categoras:
Constructores (y distribuidores) de TADs.
Utilizadores de TADs hechos por otros.
El usuario de un TAD sabe de su comportamiento a partir de las especificaciones
tanto semnticas como sintcticas que le habrn sido proporcionadas por el creador del
mismo.
Veamos, a partir de unos sencillos ejemplos, la forma de usar TADs (queda
pendiente cmo construirlos: se ver en los temas posteriores). Se trata de dos tipos
abstractos de datos: Pilas y Colas que constituyen fenmenos observables con mucha
frecuencia en el mundo real.

Esta idea permite introducir el segundo concepto ms representativo de la Programacin Orientada a


Objetos: la HERENCIA

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 53

2.2. TAD PILA DE NMEROS ENTEROS.


2.2.1. Concepto.
Una pila es una agrupacin de elementos de determinada naturaleza o tipo (datos de
personas, nmeros, procesos informticos, automviles, etc.) entre los que existe definida
una relacin de orden (estructura de datos). En funcin del tiempo, algunos elementos de
dicha naturaleza pueden llegar a la pila o salir de ella (operaciones / acciones). En
consecuencia el estado de la pila vara.
Una pila presenta el comportamiento LIFO (Last Input First Output) y el criterio de
ordenacin se establece en sentido inverso al orden de llegada. As pues, el ltimo elemento
que lleg al conjunto ser el primero en salir del mismo, y as sucesivamente.
2.2.2. Modelo grfico
Podramos representar grficamente una pila segn aparece en la figura 2.1: una
estructura de datos vertical, en la que los elementos se insertan y extraen por la parte
superior.
desapilar

apilar
5
4
1
7
2

cima

fondo

Figura 2.1. Modelo grfico de Pila


2.2.3. Especificaciones
A continuacin se describen las operaciones que vamos a poder realizar sobre el TAD
pila de enteros. Para ello, utilizaremos dos tipos de especificaciones:
o Especificaciones sintcticas. Hacen referencia a los aspectos sintcticos de las
distintas operaciones asociadas al TAD: nombre de la operacin, parmetros,
resultado devuelto por la operacin, tipo de dicho resultado, etc.
o Especificaciones semnticas. Indican el efecto producido por la operacin, es
decir, la definen desde el punto de vista de lo que hace.
En la Tabla 2.1., se definen las especificaciones semnticas y sintcticas del TAD pila
de enteros. Cuando se desee utilizar el tad pila en un programa, habr que poner la siguiente
sentencia en la primera lnea del programa, despus de la cabecera (o bien utilizarlo dentro
del mismo paquete package tadPila):
import tadPila.*;

54 TIPOS ABSTRACTOS DE DATOS

Operacin
2
apilar

desapilar

pilaVacia

leerPila
imprimirPila
numElemPila
cima
decapitar
eliminarPila

Especificacin semntica
Mtodo que entrega un elemento (x) para
que quede incorporado en la cima de la
pila.
Mtodo que elimina el elemento que ocupa
la cima de la pila y devuelve como
resultado dicho elemento.
Mtodo que al ejecutarse devuelve true si la
pila est vaca (no tiene elementos), y false
en caso contrario.
Mtodo que se utiliza para realizar la carga
inicial de elementos de la pila.
Mtodo que muestra en la pantalla el
contenido de la pila.
Mtodo que devuelve el nmero de
elementos de la pila.
Mtodo que devuelve la cima de la pila (sin
alterarla).
Mtodo que elimina el elemento de la cima
de la pila.
Mtodo que recibe una pila (que puede
tener elementos o no) y la devuelve vaca.

ESTRUCTURAS DE DATOS

Especificacin sintctica
void apilar (int x)

int desapilar () throws PilaVacia

boolean pilaVacia ()

void leerPila () throws NumberFormatException,


IOException
void imprimirPila ()
int numElemPila ()
int cima () throws PilaVacia
void decapitar ()throws PilaVacia
void eliminarPila ()

Tabla 2.1. Especificaciones semnticas y sintcticas del TAD Pila de enteros.


2.2.4. Interfaz del TAD Pila.
El interfaz Pila define los mtodos de objeto que utilizaremos con la clase TadPila:
import java.io.*;
public interface Pila {
boolean pilaVacia ();
void eliminarPila ();
int cima () throws PilaVacia;
void apilar (int x);
int desapilar () throws PilaVacia;
void decapitar () throws PilaVacia;
void imprimirPila ();
void leerPila () throws NumberFormatException, IOException;
int numElemPila ();
}

Se puede producir una situacin de excepcin cuando el nmero de elementos de la pila supere determinado
valor (lgicamente no podrn ser infinitos). Dicha situacin se manifestar al intentar ejecutar la operacin de
apilar sobre una pila en dicho estado.
3
Se produce una excepcin (PilaVacia) si se intenta desapilar de una pila vaca. Lo mismo ocurre con las
operaciones cima y decapitar.
4
Puede resultar innecesario y forzado en Java, pero queremos transmitir la idea de la obligacin de crear y
destruir los objetos cuando se trabaje en Programacin Orientada a Objetos.

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 55

2.2.5. Prueba del TAD pila.


2.2.5.1. Condiciones normales.
En el siguiente algoritmo se va a realizar una verificacin del funcionamiento
elemental (sin situaciones de excepcin) del TAD Pila. Primero se inicializa la pila, a
continuacin se introducen varios elementos en la misma, y por ltimo se sacan dos de los
datos, escribiendo los elementos desapilados en la pantalla.
public class PruebaPila1 {
public static void main (String[] args) throws PilaVacia {
Pila p = new TadPila ();
int x;
p.apilar (1);
p.apilar (2);
p.apilar (3);
p.apilar (11);
p.apilar (15);
x = p.desapilar ();
System.out.println ("x = " + x);
x = p.desapilar ();
System.out.println ("x = " + x);
p.eliminarPila ();
}
}

2.2.5.2. Situaciones de excepcin.


A continuacin se muestra un ejemplo mediante el cual podemos verificar el
funcionamiento del TAD frente a situaciones de excepcin: introducimos una serie de
elementos en la pila, y luego intentamos desapilar uno ms de los que tena la pila
originalmente.
public class PruebaPila2 {
public static void main(String[] args) throws PilaVacia {
Pila pila1 = new TadPila ();
int i, j;
System.out.println ();
for (i = 1; i < 10; i++)
pila1.apilar (i);
j = pila1.desapilar ();
for (i = 1; i < 10; i++)
j = pila1.desapilar ();
pila1.eliminarPila ();
}
}

56 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

2.2.6. Algoritmos bsicos con pilas.


En los siguientes apartados se desarrollan algunos ejemplos elementales que hacen
uso del TAD pila siguiendo las especificaciones indicadas en el apartado anterior. En
general se tendrn en cuenta las siguientes consideraciones:
La pila es una estructura muy adecuada para el tratamiento recursivo por lo que se insta
a su empleo. Con frecuencia las especificaciones de los enunciados fuerzan a ello tanto
por indicacin expresa como por la exigencia del cumplimiento simultneo de las
condiciones de realizar un nico recorrido (no desapilar/apilar cada elemento ms de
una vez) y no utilizar estructuras de datos auxiliares.
As pues, en general el tratamiento de pilas se har siguiendo la mecnica desapilar
llamada recursiva apilar. El proceso de los elementos de la pila se realizar, segn
los casos, a la ida, a la vuelta, o parcialmente en ambas fases.
La condicin de terminacin pesimista supone el cumplimiento (true) de la condicin
pila.pilaVacia (). La terminacin anticipada se ejecuta de forma implcita no realizando
llamadas posteriores cuando se cumpla determinada circunstancia. En este caso se
deber prestar atencin especial al tratamiento del ltimo elemento desapilado (si se
desea volver a apilarlo o no). El resto de elementos de la pila (desde el actual hasta el
fondo) permanece inalterado.
2.2.6.1. Sin terminacin anticipada.
2.2.6.1.1. Escribir el contenido de una pila en pantalla.
Se muestra un mtodo (escribirPila) que consiste en vaciar la pila (desapilar) durante
la fase de ida. A continuacin, se escribe en la pantalla el elemento desapilado y se hace
una llamada recursiva. A la vuelta de la recursividad, se debern apilar los sucesivos
valores almacenados localmente en las diferentes instancias de la variable elem (local).
El nico argumento que necesita el mtodo es la propia pila. A continuacin se
muestra el algoritmo.
static void escribirPila (Pila pila) throws PilaVacia {
int elem;
if (!pila.pilaVacia ()) {
elem = pila.desapilar ();
System.out.println (elem);
escribirPila (pila);
pila.apilar (elem);
}
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 57

2.2.6.1.2. Contar los elementos de una pila.


Se muestra un mtodo que consiste en vaciar la pila (desapilar) durante la fase de
ida. En cada llamada se incrementa en una unidad el resultado de la variable local (resul)
que, en cada paso de la fase de vuelta, se devuelve como resultado del mtodo. As
mismo se debern apilar los sucesivos valores almacenados localmente en las diferentes
instancias de la variable elem (local).
No existe finalizacin anticipada y la inicializacin se produce en el momento de
transicin del proceso recursivo (al alcanzar la condicin de terminacin, cuando la pila
est vaca).
El nico argumento que necesita el mtodo es la propia pila. A continuacin se
muestra el algoritmo.
static int contarPila (Pila pila) throws PilaVacia {
int elem, resul;
if (! pila.pilaVacia ()) {
elem = pila.desapilar ();
resul = 1 + contarPila(pila);
pila.apilar (elem);
}
else resul = 0;
return resul;
}

2.2.6.1.3. Obtener el duplicado de una pila.


Se trata de obtener un duplicado de la pila que se pasa como argumento. En
consecuencia el algoritmo necesitar dos argumentos: la pila origen (pilaO) y la pila destino
(pilaD).
En este caso tampoco existe la posibilidad de terminacin anticipada sino que
seguiremos procesando los datos hasta que se cumpla la condicin pilaO.pilaVacia (). La
construccin del duplicado de la pila (pilaD) se consigue apilando en ella a la vez que se
reconstruye la original (en la fase de vuelta).
A continuacin se muestra el algoritmo.
static void copiarPila (Pila pilaO, Pila pilaD) throws PilaVacia {
int elem;
if (! pilaO.pilaVacia ()) {
elem = pilaO.desapilar ();
copiarPila (pilaO, pilaD);
pilaO.apilar (elem);
pilaD.apilar (elem);
}
}

Si se deseara obtener un resultado en orden inverso al inicial apilaramos en la pila


destino (pilaD) en la fase de ida, utilizando el siguiente algoritmo:

58 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

static void copiarPilaI (Pila pilaO, Pila pilaD) throws PilaVacia {


int elem;
if (! pilaO.pilaVacia ()) {
elem = pilaO.desapilar ();
pilaD.apilar (elem);
copiarPilaI (pilaO, pilaD);
pilaO.apilar (elem);
}
}

2.2.6.1.4. Sumergir un elemento en una pila.


Este ejemplo, permite implementar una operacin contraria a la naturaleza de la pila.
Consiste en un mtodo que introduce un dato en el fondo de la pila.
Para poder colocar un elemento en el fondo de la pila, se procede a vaciar la pila, y en
el momento que est vaca se apila el dato (transicin entre las fases de ida y de
vuelta).
static void sumergir (Pila pila, int dato) throws PilaVacia {
int elem;
if (!pila.pilaVacia ()) {
elem = pila.desapilar ();
sumergir (pila, dato);
pila.apilar (elem);
}
else pila.apilar (dato);
}

2.2.6.1.5. Invertir los elementos de una pila.


Dada una pila de nmeros enteros, vamos a implementar un mtodo que la devuelva
con su contenido invertido. Para ello, utilizaremos el mtodo sumergir que hemos visto en
el apartado anterior: el mtodo invertir consistir en desapilar un elemento, llamar
recursivamente, y a la vuelta de la recursividad, en vez de apilar el elemento en la pila, lo
sumergiremos en el fondo de la misma. Ntese que al utilizar dos mtodos recursivos, cada
uno de los elementos de la pila se desapilar y apilar ms de una vez.
static void invertir (Pila pila) throws PilaVacia {
int elem;
if (!pila.pilaVacia ())
{
elem = pila.desapilar ();
invertir (pila);
sumergir (pila, elem);
}
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 59

2.2.6.2. Terminacin anticipada.


Como ya se ha indicado en algunos casos no ser necesario que se alcance la
condicin de parada en la ejecucin del programa (cuando se cumpla determinada
circunstancia). Para ello basta con que el algoritmo no ejecute una nueva llamada recursiva,
no requirindose el empleo de variables de control ni ninguna lgica complementaria.
2.2.6.2.1. Obtener el elemento del fondo de una pila.
Este ejemplo permite implementar una operacin contraria a la naturaleza de la pila
(ya que debemos llegar a vaciarla para localizar el ltimo elemento).
En el siguiente mtodo se muestra una posible solucin que permite identificar, en la
fase de ida, cul es el elemento del fondo. Cada vez que desapilamos un elemento,
verificamos si siguen quedando elementos. En caso afirmativo, hacemos una nueva llamada
recursiva y a la vuelta apilamos el elemento desapilado. Cuando la pila se ha quedado
vaca, acabamos de desapilar el ltimo elemento: guardamos en la variable dato el ltimo
elemento de la pila (elem), y no hacemos ms llamadas recursivas. En el caso de que la pila
estuviese originalmente vaca devolveramos un mensaje de error (y como resultado de
error devolveramos -9999).
public static int desfondar (Pila p) throws PilaVacia {
int elem, dato;
if (!p.pilaVacia ()) {
elem = p.desapilar ();
if (!p.pilaVacia ()) {
dato = desfondar(p);
p.apilar (elem);
}
else dato = elem;
}
else {
System.out.println ("error, la pila est vaca");
dato = -9999;
}
return dato;
}

60 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

2.2.6.2.2. Comprobar si un elemento pertenece a una pila.


El siguiente ejemplo muestra un mtodo (esta) que devuelve un valor booleano
dependiendo de que durante el transcurso de exploracin de la pila encuentre o no un valor
(dato) que se pasa como argumento.
En esencia el algoritmo consiste en vaciar la pila (condicin pesimista) verificando en
cada llamada si el elemento desapilado coincide con el dato. En caso negativo se genera
una nueva llamada recursiva, pero en caso afirmativo se prepara el valor a devolver por el
mtodo (resul = true) y no se realiza una nueva llamada recursiva. Si se llega a vaciar la
pila, se entender que el elemento buscado no existe y, en consecuencia, el mtodo
devolver el valor false.
static boolean esta (Pila pila, int dato) throws PilaVacia {
int elem;
boolean resul;
if (!pila.pilaVacia ()) {
elem = pila.desapilar ();
if (elem == dato)
resul = true;
else resul = esta (pila, dato);
pila.apilar (elem);
}
else resul= false;
return resul;
}

2.2.6.3. Mezclar dos pilas.


La dificultad principal de este tipo de procesos se deriva de la naturaleza
destructiva de la operacin de lectura (para conocer el elemento de la cima de una pila
hay que quitarlo de ella desapilar-). Esto implica que, en un tratamiento recursivo, es
posible que algn valor desapilado de alguna de las pilas en instancias anteriores est
durante algn tiempo pendiente de ser procesado.
El mtodo, con carcter general, consiste en realizar un tratamiento recursivo cuya
estructura es la siguiente:
Fase de ida:
Se desapilar de una de las dos pilas o de ambas.
Se realiza el tratamiento especfico. Dicho tratamiento deber incluir el mecanismo de
informar a la siguiente instancia, mediante los argumentos apropiados, de qu pila o
pilas deber desapilarse, as como los elementos desapilados en la propia instancia o en
alguna instancia anterior (y propagados como argumento en el proceso recursivo),

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 61

(elem1, elem2)5. Para ello se utilizan los argumentos booleanos apilar1 y apilar2. Su
interpretacin es: un valor true indica que el elemento (elem1|2) an no se ha tratado,
por lo que queda pendiente de apilar en la fase de vuelta (y, en su caso, de tratar) y no
deber desapilarse de la pila correspondiente en la siguiente instancia.
Se realiza una llamada recursiva con los argumentos actualizados durante el tratamiento
anterior.
Se escribe el cdigo que deber ejecutarse la fase de vuelta correspondiente a la
instancia actual que, entre otros posibles aspectos, deber ocuparse de (re)apilar los
elementos desapilados en dicha instancia.
Consideracin inicial: Los argumentos apilar1 y apilar2 deben inicializarse a false
(no hay ningn elemento pendiente por tratar). Los valores iniciales de elem1 y elem2 no
tienen relevancia.
Condicin de terminacin:
Obsrvese que el hecho de recibir una pila vaca es una condicin necesaria para dar
por finalizado su tratamiento, pero no suficiente. Es posible que el elemento del fondo de
una pila se haya desapilado en instancias anteriores, por lo que la pila se propaga vaca pero
aun no se ha tratado su ltimo elemento. As pues una pila se mantiene vigente en el
proceso recursivo si:
!pila1|2.pilaVacia () || apilar1|2

Para facilitar la legibilidad usaremos las variables locales booleanas6


pend1|2 = (!pila1|2.pilaVacia () || apilar1|2)

La cabecera del mdulo deber usar como argumentos:


o pila1 y pila2, de tipo Pila,;
o apilar1 y apilar2, de tipo boolean
o elem1 y elem2 de tipo int
Fase de transicin:
Para saber cundo debemos parar de hacer llamadas recursivas, tenemos dos casos
posibles:
La naturaleza del tratamiento es tal que el proceso finaliza cuando se acaba con el
tratamiento de una de las pilas (interseccin o modalidad AND). El tratamiento
recursivo se mantiene si (pend1 && pend2) == true (ambas pilas estn pendientes de

En este caso elem1, elem2 no se utilizan como variables locales tal como se hace en el caso de tratamientos
con una sola pila).
6
Entendidas como pendiente.

62 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

tratar). Se debern apilar (y, en su caso, procesar) los elementos pendientes desapilados
en instancias anteriores (identificados mediante la condicin apilar1|2 == true). La fase
de transicin finaliza y se inicia la de vuelta. (Un posible ejemplo sera: obtener una
pila con los elementos comunes de otras dos).
Para que el tratamiento acabe es necesario procesar ambas pilas (unin o modalidad
OR). (Por ejemplo, si se desea obtener una pila con los elementos de otras dos). En este
caso:
o Si una pila est procesada (pend1|2 == false) y la otra no (pend2|1 == true):
Se (re)apila, si procede, el elemento desapilado de la pila pendiente de procesar
(apilar2|1) y se realiza, si procede, el tratamiento oportuno con dicho elemento
(elem2|1).
Se invoca a un (nuevo) tratamiento recursivo que opera nicamente sobre la pila
pendiente de procesar.
o Si ambas pilas estn procesadas por completo (pend1 == false y pend2 == false), el
proceso de transicin finaliza y se inicia la fase de vuelta.
Fase de vuelta: Desde las correspondientes instancias se realiza el tratamiento
adecuado que deber contemplar, normalmente, la restitucin (apilar) de los valores
desapilados en la fase de ida.
2.2.6.3.1. Ejemplo1. Realizar la interseccin de dos pilas.
Dadas dos pilas (pila1 y pila2) ordenadas ascendentemente desde la cima hacia el
fondo (sin elementos repetidos en cada una de las pilas), se desea obtener una nueva pila
(pila3), tambin ordenada ascendentemente y sin elementos repetidos, con los elementos
comunes de ambas.
Aplicando a este caso el tratamiento genrico, se trata de:
Fase de ida. Desapilar si procede (en funcin del valor de los argumentos apilar1 y
apilar2. Existen los siguientes casos:
o elem1 > elem2. Habr que (re)apilar elem2 en pila2 en la fase de vuelta. La llamada
recursiva se hace con apilar1 = true y apilar2 = false.
o

elem1 < elem2. Tendremos que (re)apilar elem1 en pila1 en la fase de vuelta. La
llamada recursiva se hace con apilar1 = false y apilar2 = true.

o elem1 == elem2. En la fase de vuelta se puede apilar cualquiera de los dos


elementos (slo una vez) en pila3, y reapilar en pila1 y pila2. La llamada recursiva
se
hace
con
apilar1 = false y apilar2 = false.
Condicin de terminacin. El proceso finaliza cuando se acaba con el tratamiento de
una de las pilas (pend1 && pend2) == false.

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 63

Fase de transicin: Si queda algn elemento pendiente en alguna de las pilas


(apilar1 == true o apilar2 == true), habr que apilarlo en la pila que corresponda.
Las siguientes figuras muestran un ejemplo.
apilar1
false
ep1
?

2
4
77
pila1

apilar2
false
ep2
?

1
2
3
4
5
6
7
pila2

pila3

Figura 2.2. Situacin de partida.


apilar1
indif
ep1
indif

2
4
77
pila1

apilar2
indif
ep2
indif

Figura 2.3. Situacin final


A continuacin aparece el cdigo.

1
2
3
4
5
6
7
pila2

2
4
pila3

64 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

static void mezclarPilaAND (Pila pila1, Pila pila2, Pila pila3, boolean apilar1,
boolean apilar2, int elem1, int elem2) throws PilaVacia {
boolean pend1, pend2;
pend1 = !pila1.pilaVacia () || apilar1;
pend2 = !pila2.pilaVacia () || apilar2;
if (pend1 && pend2) {
if (!apilar1)
elem1 = pila1.desapilar ();
if (!apilar2)
elem2 = pila2.desapilar ();
if (elem1 < elem2) {
mezclarPilaAND (pila1,pila2,pila3,false,true,elem1,elem2);
pila1.apilar (elem1);
}
else if (elem2 < elem1) {
mezclarPilaAND (pila1,pila2,pila3,true,false,elem1,elem2);
pila2.apilar (elem2);
}
else {
mezclarPilaAND (pila1,pila2,pila3,false,false,elem1,elem2);
pila1.apilar (elem1);
pila2.apilar (elem2);
pila3.apilar (elem1);
}
}
else if (apilar1)
pila1.apilar (elem1);
else if (apilar2)
pila2.apilar (elem2);
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 65

2.2.6.3.2. Ejemplo 2. Realizar la unin de dos pilas.


Dadas dos pilas (pila1 y pila2) ordenadas ascendentemente desde la cima hacia el
fondo (sin elementos repetidos en cada una de las pilas), se desea obtener una nueva pila
(pila3), tambin ordenada ascendentemente y sin elementos repetidos, con los elementos de
ambas.
Aplicando a este caso el tratamiento genrico, se trata de:
Fase de ida. Desapilar si procede (en funcin del valor de los argumentos apilar1 y
apilar2. Se pueden dar los siguientes casos:
o elem1 > elem2. Habr que apilar elem2 en pila3 (y reapilar en pila2) en la fase de
vuelta. La llamada recursiva se hace con apilar1 = true y apilar2 = false.
o

elem1 < elem2. Tendremos que apilar elem1 en pila3 (y reapilar en pila1) en la fase
de vuelta. La llamada recursiva se hace con apilar1 = false y apilar2 = true.

o elem1 == elem2. En la fase de vuelta se puede apilar cualquiera de los dos


elementos (solo una vez) en pila3, y reapilar en pila1 y pila2. La llamada recursiva
se hace con apilar1 = false y apilar2 = false.
Condicin de terminacin. Para que el tratamiento acabe es necesario procesar ambas
pilas (pend1 || pend2) == false
Fase de transicin
o Si se ha terminado con pila1 (pend1 == true) y no con pila2 (pend2 == false).
Se aplical mtodo copiarPila con efectos sobre el estado actual de pila2.
Se restaura, si procede, el ltimo valor desapilado de pila1.
Se inicia la fase de vuelta
o Si se ha terminado con pila2 (pend2 == true) y no con pila1 (pend1= = false).
Se aplica el mtodo copiarPila con efectos sobre el estado actual de pila1.
Se restaura, si procede, el ltimo valor desapilado de pila2.
Se inicia la fase de vuelta.
o Si se ha terminado con ambas pilas (pend1 == false y pend2 == false).
Se inicia la fase de vuelta.
Las siguientes figuras muestran un ejemplo:

66 TIPOS ABSTRACTOS DE DATOS

apilar1
false
ep1
?

ESTRUCTURAS DE DATOS

2
4
77
pila1

apilar2
false
ep2
?

1
2
3
4
5
6
7
pila2
pila3

Figura 2.4. Situacin de partida.


apilar1
indif
ep1
indif

2
4
77
pila1

apilar2
indif
ep2
indif

Figura 2.5. Situacin final


A continuacin se muestra el cdigo.

1
2
3
4
5
6
7
pila2

1
2
3
4
5
6
7
77
pila3

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 67

static void copiarPila (Pila pilaO, Pila pilaD) throws PilaVacia

int elem;
if (! pilaO.pilaVacia ()) {
elem = pilaO.desapilar ();
copiarPila (pilaO, pilaD);
pilaO.apilar (elem);
pilaD.apilar (elem);
}
}
static void mezclarPilaOR (Pila pila1, Pila pila2, Pila pila3, boolean apilar1,
boolean apilar2, int elem1, int elem2) throws PilaVacia {
boolean pend1, pend2;
pend1 = !pila1.pilaVacia () || apilar1;
pend2 = !pila2.pilaVacia () || apilar2;
if (pend1 && pend2) {
if (!apilar1)
elem1 = pila1.desapilar ();
if (!apilar2)
elem2 = pila2.desapilar ();
if (elem1 < elem2) {
mezclarPilaOR (pila1, pila2, pila3, false, true, elem1, elem2);
pila1.apilar (elem1);
pila3.apilar (elem1);
}
else if (elem2 < elem1) {
mezclarPilaOR (pila1,pila2,pila3,true,false,elem1,elem2);
pila2.apilar (elem2);
pila3.apilar (elem2);
}
else {
mezclarPilaOR (pila1,pila2,pila3,false,false,elem1,elem2);
pila1.apilar (elem1);
pila2.apilar (elem2);
pila3.apilar (elem1);
}
}
else if (pend1 && !pend2) {
copiarPila (pila1, pila3);
if (apilar1) {
pila1.apilar (elem1);
pila3.apilar (elem1);
}
}
else if (pend2 && !pend1) {
copiarPila (pila2, pila3);
if (apilar2) {
pila2.apilar (elem2);
pila3.apilar (elem2);
}
}
}

68 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

2.2.6.4. Mezcla de pilas con terminacin anticipada.


Dadas dos pilas (pila1 y pila2) ordenadas ascendentemente desde la cima hacia el
fondo (sin elementos repetidos en cada una de las pilas), se desea realizar un mtodo
booleano que indique si los elementos de pila2 estn contenidos en pila1 o no.
Esta situacin se puede resolver a partir de la consideracin de mezcla de pilas en la
modalidad AND (termina cuando se ha procesado por completo cualquiera de las pilas).
En este caso se produce una situacin de terminacin anticipada cuando, en su caso,
aparece por primera vez un elemento de pila2 que no est en pila1 (elem1 > elem2). El
proceso debe terminar y el mtodo devuelve false.
En caso contrario (terminacin pesimista) pueden darse las siguientes
circunstancias:
Se ha procesado completa pila1 (pend1 == false) pero pila2 no (pend2 == true). An
quedan elementos en pila2 por tanto no cumple la condicin de estar contenida en
pila1, y en consecuencia el resultado ser false.
Se ha procesado pila2 completa (pend2 == false) y pero no pila1 (pend1 == true). Por
tanto, pila2 estar contenida en pila1; y en consecuencia el resultado ser true.
Se ha conseguido llegar a la terminacin pesimista (se han procesado completamente
ambas pilas: pend1 == false y pend2 == false). Por lo tanto, pila2 est contenida en
pila1 y el mtodo devolver el valor true.
Aplicando a este caso el tratamiento genrico, se trata de:
Fase de ida. Desapilar si procede (en funcin del valor de los argumentos apilar1 y
apilar2). Se pueden dar los siguientes casos:
o elem1 > elem2. Hay que reapilar elem2 en pila2 en la fase de vuelta. Es imposible
que la pila2 se encuentre contenida en la pila1, por lo que habr que devolver false
como resultado.
o

elem1 < elem2. Hay que reapilar elem1 en pila1 en la fase de vuelta. La llamada
recursiva se hace con apilar1 = false y apilar2 = true.

o elem1 == elem2. En la fase de vuelta se reapilan los dos elementos en pila1 y pila2.
La llamada recursiva se hace con apilar1 = false y apilar2 = false.
Condicin de terminacin. El proceso finaliza cuando se acaba con el tratamiento de
una de las pilas (pend1 && pend2) == false.
Fase de transicin
o Si queda algn elemento pendiente de tratar en alguna de las pilas (apilar1 == true
o apilar2 == true), habr que apilarlo en la pila que corresponda y asignar el
resultado correspondiente al mtodo.

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 69

El cdigo se muestra a continuacin:


static boolean contenida (Pila pila1, Pila pila2, boolean apilar1, boolean
apilar2, int elem1, int elem2) throws PilaVacia {
boolean pend1, pend2, resul;
pend1 = !pila1.pilaVacia () || apilar1;
pend2 = !pila2.pilaVacia () || apilar2;
if (pend1 && pend2) {
if (!apilar1)
elem1 = pila1.desapilar ();
if (!apilar2)
elem2 = pila2.desapilar ();
if (elem1 < elem2) {
resul = contenida (pila1, pila2, false, true, elem1, elem2);
pila1.apilar (elem1);
}
else if (elem2 < elem1) {
resul = false;
pila2.apilar (elem2);
pila1.apilar (elem1);
}
else{
resul = contenida (pila1,pila2,false,false,elem1,elem2);
pila1.apilar (elem1);
pila2.apilar (elem2);
}
}
else if (apilar1) {
resul = true;
pila1.apilar (elem1);
}
else if (apilar2) {
resul = false;
pila2.apilar (elem2);
}
else resul = true;
return resul;
}

70 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

2.3.
2.4.2.3. TAD COLA DE NMEROS ENTEROS.
2.4.1.2.3.1. Concepto.
Una cola es una agrupacin de elementos de determinada naturaleza o tipo (datos de
personas, nmeros, procesos informticos, automviles, etc.) entre los que existe definida
una relacin de orden (estructura de datos). En funcin del tiempo, algunos elementos
pueden llegar a la cola o salir de ella (operaciones / acciones). En consecuencia el estado
de la cola vara.
Una cola presenta comportamiento FIFO (First Input First Output) y se respeta como
criterio de ordenacin el momento de la llegada: el primer elemento de la cola, ser el que
primero lleg a ella y, en consecuencia, el primero que saldr, y as sucesivamente.
2.4.2.2.3.2. Modelo grfico.
Podramos representar grficamente una cola segn aparece en la Figura 2.6: una
estructura de datos horizontal, en la que los elementos se insertan por el extremo derecho, y
se extraen por la parte izquierda.
desencolar

principio
fin
5 1 4 3 2 7 9 6 8

encolar

Figura 2.6. Modelo grfico de Cola


2.4.3.2.3.3. Especificaciones
En la Tabla 2.2., que aparece en la pgina siguiente, se definen las especificaciones
semnticas y sintcticas del TAD cola de enteros. Utiliza el tipo de datos TadCola (cola de
nmeros enteros). Cuando se desee utilizar el tad cola, habr que poner la siguiente
sentencia en la primera lnea del programa, despus de la cabecera (o bien utilizarlo dentro
del mismo paquete package tadCola):
import tadCola.*;

ESTRUCTURAS DE DATOS

Operacin
7
encolar
desencolar

colaVacia

leerCola
imprimirCola

invertirCola
numElemCola
primero
quitarPrimero
eliminarCola

Especificacin semntica
Mtodo que entrega un elemento (x) para
que quede incorporado al final de la cola.
Mtodo que elimina el elemento de la cola
que ocupa el primer lugar, devolvindolo
como resultado.
Mtodo que al ejecutarse devuelve true si
la cola est vaca, y false en caso
contrario.
Mtodo mediante el que se produce la
carga inicial de elementos de la cola.
Mtodo que muestra en el dispositivo de
salida (pantalla) el contenido actual de la
cola.
Mtodo que devuelve la cola con sus
elementos invertidos
Mtodo que devuelve el nmero de
elementos de la cola.
Mtodo que devuelve el primer elemento
de la cola sin desencolarlo.
Mtodo que elimina el primer elemento de
la cola.
Mtodo que recibe una cola (que puede
tener elementos o no) y la devuelve vaca.

TIPOS ABSTRACTOS DE DATOS 71

Especificacin sintctica
void encolar (int x)
int desencolar () throws ColaVacia

boolean colaVacia ()

void leerCola () throws NumberFormatException,


IOException
void imprimirCola ()

void invertirCola () throws ColaVacia


int numElemCola ()
int primero () throws ColaVacia
void quitarPrimero () throws ColaVacia
void eliminarCola ()

Tabla 2.2. Especificaciones semnticas y sintcticas del TAD Cola de enteros.


2.4.4.2.3.4. Interfaz del TAD Cola.
import java.io.IOException;
public interface Cola {
boolean colaVacia ();
void eliminarCola ();
int primero () throws ColaVacia;
void encolar (int x);
int desencolar () throws ColaVacia;
void quitarPrimero () throws ColaVacia;
void mostrarEstadoCola ();
void imprimirCola ();
void leerCola () throws NumberFormatException, IOException;
int numElemCola ();
void invertirCola () throws ColaVacia;
}

Se puede producir una situacin de excepcin cuando el nmero de elementos de la cola supere determinado
valor (lgicamente no podrn ser infinitos) y se intente encolar sobre una cola en dicho estado.
8
Se produce una excepcin (ColaVacia) si se intenta desencolar de una cola vaca. Lo mismo ocurre con las
operaciones primero y quitarPrimero.

72 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

2.4.5.2.3.5. Prueba del TAD Cola.


2.4.5.1.2.3.5.1.

Condiciones normales.

El siguiente algoritmo simplemente se trata de introducir y extraer elementos de la


cola sin provocar situaciones de excepcin.
public class PruebaCola1 {
public static void main (String [ ] args) throws ColaVacia {
Cola cola1 = new TadCola ();
int Elem;
cola1.encolar (8);
cola1.encolar (7);
cola1.encolar (9);
cola1.encolar (11);
Elem = cola1.desencolar ();
Elem = cola1.desencolar ();
System.out.println ("Acaba de salir el numero "+Elem);
cola1.eliminarCola ();
}
}

2.4.5.2.2.3.5.2.

Situaciones de excepcin.

En el siguiente ejemplo se verifica el funcionamiento del TAD frente a situaciones de


excepcin.
public class PruebaCola2 {
public static void main (String [ ] args) throws ColaVacia {
Cola cola1 = new TadCola ();
int i, j;
for (i = 1; i < 10; i++)
cola1.encolar (i);
j = cola1.desencolar ();
System.out.println ("Hemos sacado " + j);
for (i = 1; i < 10; i++) {
j = cola1.desencolar ();
System.out.println("Hemos sacado " + j);
}
cola1.eliminarCola ();
}
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 73

2.4.6.2.3.6. Algoritmos bsicos con colas.


La filosofa general para la solucin para el tratamiento de pilas, mediante tcnicas
recursivas, basada en un esquema de desapilar llamada recursiva apilar con una
condicin de finalizacin vinculada a la situacin de estructura vaca no dar en general
buenos resultados con colas, dado que devolvera la estructura con sus elementos
cambiados de orden respecto a la situacin inicial (devolver la cola invertida).
Tampoco sera vlido un planteamiento del tipo de desencolar encolar llamada
recursiva, pues nunca se alcanzara la condicin de finalizacin.
En consecuencia para basarse en la primera idea resultar, casi siempre, necesario
ejecutar un proceso complementario de invertir los elementos de la cola (como alternativa
podra usarse una estructura de datos auxiliar).
El proceso se simplifica notablemente si se conoce a priori el nmero inicial de
elementos de la cola pues a partir de este valor se podra utilizar la segunda idea utilizando
como condicin de finalizacin el valor de una variable, inicialmente con el valor del
nmero de elementos de la cola, que se decrementara con cada llamada al algoritmo.
Tambin podra procederse en este caso a un tratamiento iterativo.
2.4.6.1.2.3.6.1.

Invertir el orden de los elementos de una cola.

Si deseamos invertir una cola, y suponiendo que no se conoce el nmero de


elementos que contiene, ni tenemos invertirCola entre las operaciones del TAD, se propone
una solucin recursiva, acorde con la tendencia a invertirla que produce este tipo de
tratamiento.
static void invertir (Cola cola) throws ColaVacia {
int elem;
if (!cola.colaVacia ()) {
elem = cola.desencolar ();
invertir (cola);
cola.encolar (elem);
}
}

2.4.6.2.2.3.6.2.

Contar los elementos de una cola.

Si hay que hacer un mtodo que cuente los elementos de una cola, evidentemente no
se conoce a priori el nmero de elementos. La nica posibilidad para realizar el mtodo es
un tratamiento recursivo que, aunque cuente correctamente el nmero de elementos de la
cola, los devolvera en el orden contrario al inicial. Se necesita, pues, una ejecucin (antes o
despus) del mtodo visto en el apartado anterior (invertir) fuera del tratamiento recursivo.

74 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

static void invertir (Cola cola) throws ColaVacia {


int elem;
if (!cola.colaVacia ()) {
elem = cola.desencolar ();
invertir (cola);
cola.encolar (elem);
}
}
static int contarCola (Cola cola) throws ColaVacia {
int elem, resul;
if (! cola.colaVacia ()) {
elem = cola.desencolar ();
resul = 1 + contarCola (cola);
cola.encolar (elem);
}
else resul = 0;
return resul;
}
static int cuentaCola (Cola cola) throws ColaVacia {
invertir (cola);
return contarCola (cola);
}

2.4.6.3.2.3.6.3.

Obtencin una cola a partir de otra.

Siempre que vayamos a trabajar con colas, tenemos tres formas posibles de hacerlo:
Si no conocemos el nmero de elementos de la cola, necesariamente tendremos que
realizar un tratamiento recursivo siguiendo el esquema desencolartratamiento
recursivoencolar. El problema que tenemos es el que ya se ha planteado
previamente: este tratamiento deja la cola invertida. Por lo tanto, posteriormente habr
que invertir la cola resultante.
Cuando conocemos el nmero de elementos de la cola, podemos optar entre las dos
siguientes posibilidades:

Hacer un tratamiento recursivo, utilizando el nmero de elementos de la cola para


fijar la condicin de parada de la recursividad. Tendremos que realizar el
tratamiento desencolarencolar antes de la recursividad.

Realizar un tratamiento iterativo, por medio de un bucle for.

Utilizando cualquiera de estos dos mtodos, los elementos de la cola se quedarn en el


mismo orden inicial.
Como ejemplo, en los siguientes subapartados se realiza la copia de una cola sobre
otra siguiendo los tres mtodos propuestos.

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 75

2.4.6.3.1.2.3.6.3.1. Sin conocer el nmero de elementos.


Necesariamente deber realizarse mediante un tratamiento recursivo que devolver la
cola original (ColaO) con sus elementos en orden contrario respecto al inicial. Para
restaurarlo se ejecutar una llamada (no recursiva) al mtodo invertir.
static void copiar (Cola colaO, Cola colaD) throws ColaVacia {
int elem;
if (!colaO.colaVacia ()) {
elem = colaO.desencolar();
colaD.encolar (elem);
copiar (colaO, colaD);
colaO.encolar (elem);
}
}
static void copiaRecursiva1 (Cola colaO, Cola colaD) throws ColaVacia {
copiar (colaO,colaD);
invertir (colaO);
}

Si lo que se desea es obtener una cola (ColaD) ordenada de forma inversa a la


original (ColaO) bastar con encolar en la fase de vuelta.
static void copiarInvertido (Cola colaO, Cola colaD) throws ColaVacia {
int elem;
if (!colaO.colaVacia ()) {
elem = colaO.desencolar();
copiarInvertido (colaO, colaD);
colaD.encolar (elem);
colaO.encolar (elem);
}
}
static void copiaRecursivaInvertida (Cola colaO, Cola colaD) throws ColaVacia {
copiarInvertido (colaO, colaD);
colaO.invertirCola ();
}

2.4.6.3.2.2.3.6.3.2. Conociendo el nmero de elementos. Tcnica recursiva.


Como ya se ha indicado, es necesario utilizar como condicin de parada el valor del
argumento n (nmero de elementos de la cola) que se decrementa en una unidad en cada
llamada.
static void copiaRecursiva2 (Cola colaO, Cola colaD, int n) throws ColaVacia {
int elem;
if (n > 0) {
elem = colaO.desencolar();
colaD.encolar (elem);
colaO.encolar (elem);
copiaRecursiva2 (colaO, colaD, n-1);
}
}

76 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

Anlogamente al caso anterior, para obtener un duplicado con los elementos


invertidos se utilizara la variante siguiente.
static void copiaRecursiva2Invertida (Cola colaO, Cola colaD, int n) throws
ColaVacia {
int elem;

if (n > 0) {
elem = colaO.desencolar ();
colaO.encolar (elem);
copiaRecursiva2Invertida (colaO, colaD, n-1);
colaD.encolar (elem);
}

2.4.6.3.3.2.3.6.3.3. Conociendo el nmero de elementos. Tcnica iterativa.


De forma similar se utiliza la variable N (nmero de elementos de la cola) como
condicin de fin de un bucle for.
static void copiaIterativa (Cola colaO, Cola colaD) throws ColaVacia {
int elem, i, n;

n = colaO.numElemCola ();
for (i = 1; i <= n; i++) {
elem = colaO.desencolar ();
colaD.encolar (elem);
colaO.encolar (elem);
}

Si lo que se pretende es obtener la cola resultante (colaD) con sus elementos en orden
contrario al inicial (colaO) sera necesario invertir posteriormente el resultado (ejecucin
del mtodo invertir) una vez ejecutado el tratamiento iterativo.
static void copiaIterativaInvertida (Cola colaO, Cola colaD) throws ColaVacia {
int elem, i, n;
n = colaO.numElemCola ();
for (i = 1; i <= n; i++) {
elem = colaO.desencolar ();
colaD.encolar (elem);
colaO.encolar (elem);
}
colaD.invertirCola ();
}

ESTRUCTURAS DE DATOS

2.4.6.4.2.3.6.4.

TIPOS ABSTRACTOS DE DATOS 77

Insertar un elemento al principio de una cola.

Se trata de introducir en la cola un dato, pasado como argumento, que no se ubicar


al final de la misma (tal como resultara como consecuencia de ejecutar la operacin
encolar (cola, dato)), sino al principio de la misma. La Figura 2.8 muestra como ejemplo el
resultado de ejecutar el algoritmo introduciendo el dato 77 en la cola cuya situacin inicial
es la representada en la Figura 2.7.

Figura 2.7. Situacin inicial de la cola.


77

Figura 2.8. Resultado despus de introducir el 77.


Se resolver de manera iterativa (conociendo el nmero de elementos). Para ello, la
insercin inicial del dato al final de la cola se produce con anterioridad a la ejecucin de la
iteracin (pero una vez contado el nmero de elementos que tiene la cola original). Se
propone al alumno la realizacin del mismo ejemplo siguiendo los otros dos mtodos
propuestos.
static void insertarPrincipioIterativo (Cola cola, int dato) throws ColaVacia {
int elem, i, n;
n = cola.numElemCola ();
cola.encolar (dato);
for (i = 1; i <= n; i++) {
elem = cola.desencolar();
cola.encolar (elem);
}
}

2.4.6.5.2.3.6.5.

Terminacin anticipada.

Como ya se ha indicado, en algunos casos no ser necesario que en la ejecucin del


programa se procese toda la estructura produciendo, en consecuencia, una terminacin
anticipada. La solucin depende de la tcnica empleada: iterativa o recursiva si se conoce el
nmero de elementos o, necesariamente recursiva si no se conoce.
En cualquier caso deber asegurarse que la estructura se devuelve en el orden
correcto, lo que implica que todos los elementos debern ser desencolados y encolados
(adems, cuando no se conoce el nmero de elementos, debern invertirse tanto la cola
completa como la parte de ella que no se haya tratado en el momento de la terminacin
anticipada). Pese a ello la terminacin anticipada tiene inters en general, dado que se
evitan copias innecesarias en la memoria en la fase de ida y en particular cuando dicho
tratamiento resulte complejo.

78 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

A continuacin se muestran ejemplos de un mtodo (esta) que devuelve un valor


booleano dependiendo de que durante el transcurso de exploracin de la cola encuentre o
no un valor (dato) que se pasa como argumento. Por ltimo, se ve un mtodo que permite
obtener el elemento final de una cola.
2.4.6.5.1.2.3.6.5.1. Verificar si hay un elemento sin conocer el nmero de elementos.
En esencia el algoritmo consiste en vaciar la cola (condicin pesimista) verificando
en cada llamada si el elemento desencolado coincide con el dato. En caso negativo se
genera una nueva llamada recursiva pero en caso afirmativo se prepara el valor a devolver
por el mtodo (resul=true) y no se realiza una nueva llamada recursiva. Deber asegurarse
que se devuelve a la cola el ltimo elemento desencolado. Si se alcanza la condicin de
terminacin se entender que el elemento buscado no existe y, en consecuencia, el mtodo
devolver el valor false.
Un problema adicional es que el tratamiento recursivo produce el desorden parcial de
los elementos de la cola. Es decir, en caso de que se produzca terminacin anticipada la
cola tendera a quedar con el siguiente aspecto:
o El bloque de elementos previo al dato encontrado quedara al final de la cola y
con sus elementos en orden inverso.
o El bloque de elementos posterior al dato encontrado quedara al principio de la
cola y con sus elementos en el orden inicial.
o El dato buscado estara situado entre ambos bloques.
Por ejemplo, si en la cola indicada en la Figura 2.9 se busca el elemento de valor 12,
el mtodo devolvera correctamente el valor true pero dejara sus elementos en la situacin
de la Figura 2.10.
2

10

12

14

16

18

20

Figura 2.9. Situacin inicial de la cola. Se busca el valor 12.


14

16

18

20

12

10

Figura 2.10. Valor 12 encontrado. Situacin actual de los elementos de la cola.


Por lo tanto, para evitar este problema, se deber utilizar un mtodo externo (previo o
posterior) de inversin de la cola inicial as como otra inversin de lo que queda de la cola
una vez encontrado el dato (antes de volver a encolarlo). A continuacin se muestra el
cdigo.

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 79

static boolean estaAux (Cola cola, int dato) throws ColaVacia {


int elem;
boolean resul;
if (! cola.colaVacia ()) {
elem = cola.desencolar ();
if (elem == dato) {
resul = true;
cola.invertirCola ();
}
else resul = estaAux (cola, dato);
cola.encolar (elem);
}
else resul = false;
return resul;
}
static boolean esta (Cola cola, int dato) throws ColaVacia {
cola.invertirCola ();
return estaAux (cola, dato);
}

2.4.6.5.2.2.3.6.5.2. Conociendo el nmero de elementos. Tcnica recursiva.


El algoritmo es ms sencillo que en el caso anterior. El proceso es un conjunto
desencolar encolar (controlado por el argumento que indica inicialmente el valor de los
elementos de la cola) en el que se pregunta si el elemento desencolado coincide con el dato
que se busca. En caso negativo se realiza una nueva llamada recursiva y en caso afirmativo
se asigna el valor que devolver el mtodo: true (no se realizan posteriores llamadas
recursivas pero habr que recolocar completamente la cola, por medio del mtodo
auxiliar recorrer). El cumplimiento de la condicin pesimista (el mdulo se ha ejecutado
tantas veces como elementos tiene la cola) implica que el mtodo devuelve el valor false.
A continuacin se muestra una posible solucin.
static boolean recorrer (Cola cola, int n) throws ColaVacia {
boolean resul;
int elem;
if (n > 0) {
elem = cola.desencolar ();
cola.encolar (elem);
resul = recorrer (cola, n-1);
}
else resul = true;
return resul;
}
static boolean estaRecursivo (Cola cola, int n, int dato) throws ColaVacia {
int elem;
boolean resul;
if (n > 0) {
elem = cola.desencolar ();
cola.encolar (elem);
if (elem == dato)
resul = recorrer (cola, n-1);
else resul = estaRecursivo (cola, n-1, dato);
}
else resul = false;
return resul;
}

80 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

2.4.6.5.3.2.3.6.5.3. Conociendo el nmero de elementos. Tcnica iterativa.


Si tenemos el nmero de elementos, y queremos realizar el tratamiento de manera
iterativa, el algoritmo ser similar al caso anterior, si bien aqu s es necesario complicar la
lgica para facilitar la terminacin anticipada (n > 0) && (!resul). Una vez que se ha
encontrado el dato, hay que continuar desencolando y encolando hasta terminar de dar la
vuelta completa a la cola (bucle for (i = 1; i<= n; i++)), como se puede ver en la siguiente
solucin:
static boolean estaIterativo (Cola cola, int dato) throws ColaVacia {
int n, elem, i;
boolean resul;
resul = false;
n = cola.numElemCola ();
while ((n > 0) && (!resul)) {
elem = cola.desencolar ();
cola.encolar (elem);
if (elem == dato)
resul = true;
n = n-1;
}
for (i = 1; i <= n; i++) {
elem = cola.desencolar ();
cola.encolar (elem);
}
return resul;
}

2.4.6.5.4.2.3.6.5.4. Obtener el elemento del final de una cola.


Lo que se propone en este apartado es conseguir un efecto similar al de la operacin
desencolar pero actuando sobre el extremo opuesto, es decir, no queremos sacar el primer
elemento de la cola, sino el ltimo que hemos insertado. Se muestra como ejemplo de
manera recursiva, sin conocer el nmero de elementos. Se propone al alumno la realizacin
del mismo ejemplo siguiendo los otros dos mtodos propuestos.
Es un caso similar al explicado en el apartado de pilas (desfondar) con la salvedad de
que aqu es necesario restaurar el orden inicial en los elementos de la cola. Para ello se
utiliza la operacin InvertirCola una vez ejecutado el tratamiento recursivo. Al contrario de
lo que ocurra en el apartado 2.3.5.6.1., en este caso excepcionalmente no hay que invertir
lo que queda de cola en la transicin, dado que en este momento la cola est vaca.

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 81

static int quitarUltimo (Cola cola) throws ColaVacia {


int elem, dato = -9999;
if (!cola.colaVacia ()) {
elem = cola.desencolar ();
if (! cola.colaVacia ()) {
dato = quitarUltimo (cola);
cola.encolar (elem);
}
else dato = elem;
}
return dato;
}
static int quitarUltimoRecursivo1 (Cola cola) throws ColaVacia {
int resul;
resul = quitarUltimo (cola);
System.out.println ("resultado "+resul);
cola.invertirCola ();
return resul;
}

2.4.6.6.2.3.6.6.

Mezclar dos colas.

De forma anloga a lo explicado en el caso de mezcla de pilas, el problema especfico


de este tipo de procesos es la naturaleza destructiva de la operacin desencolar.
El problema puede resolverse mediante los tres enfoques habituales con colas:
No se conoce el nmero de elementos. Se deber utilizar necesariamente un tratamiento
recursivo similar al explicado con pilas, pero teniendo en cuenta la circunstancia de que
las colas pueden quedar total o parcialmente desordenadas y por tanto habr que utilizar
el mdulo invertir. Se deja su resolucin como ejercicio propuesto al alumno.
Se conoce el nmero de elementos (n1 y n2). Se puede hacer:
o De manera iterativa. Se deja como ejercicio propuesto al alumno
o De manera recursiva. Se explica a continuacin.
Explicacin de la tcnica.
La tcnica es ligeramente diferente a la explicada en el caso de la mezcla de pilas. En
este caso hay que desencolar por adelantado. Es decir: en la instancia actual se desencola
de una o de ambas colas y se pasan como argumentos los elementos desencolados
(elem1|elem2). En caso de desencolar de una sola cola, el elemento de la otra se propaga a
la instancia siguiente.
Este tipo de tratamiento supone una singularidad en la primera llamada: se necesita
un mdulo (de lanzamiento), no recursivo, que desencola y encola el primer elemento de
cada una de las colas y los pasa como argumentos al mdulo recursivo.

82 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

Condicin de terminacin.
En este caso el control de la condicin de finalizacin se realiza mediante los
argumentos n1 y n2 que representan inicialmente el nmero de elementos de las respectivas
colas. Dichos argumentos debern pasarse por valor y decrementarse en cada llamada
recursiva. La condicin ((n1 == 0) || (n2 == 0)) significa que se ha desencolado el ltimo
elemento de la cola correspondiente (condicin necesaria) pero no suficiente, dado que no
se asegura que dichos elementos se hayan tratado (lo que se deber hacer en la fase
detransicin). As pues el proceso recursivo tiene lugar si (n1 >= 0 && n2 >= 0)9
Fase de ida:
Se realiza el tratamiento especfico con los elementos (elem1 y elem2) que se reciben
como argumentos.
Se desencola-encola de una de las colas o de ambas.
Se realiza una llamada recursiva.
Fase de transicin:
Se realiza, si procede, el tratamiento especfico del ejercicio en cuestin.
Se restaura, si procede, el orden establecido de las colas.
Fase de vuelta:
Se realiza, si procede, el tratamiento especfico del ejercicio en cuestin.
En general no ser necesario realizar la operacin de encolar dado que ya se ha hecho
en la fase de ida (mecanismo desencolar-encolar).
2.4.6.6.1.2.3.6.6.1. Ejemplo 1. Realizar la interseccin de dos colas.
En este ejemplo se parte de dos colas (cola1 y cola2) ordenadas ascendentemente
desde el punto de salida hacia el de llegada para obtener una nueva cola (cola3) con los
elementos comunes de ambas ordenados en el mismo sentido. La Figura 2.11. muestra un
ejemplo.
2

77

cola1
2

2
6

cola 3

cola2

Figura 2.11.Ejemplo de obtencin de una cola con los elementos comunes de otras dos

O, dicho en otros trminos: finaliza si: (n1 < 0 || n2 < 0)

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 83

Para resolver este ejemplo, utilizaremos la tcnica recursiva conociendo el nmero de


elementos. Como en este caso queremos introducir en la cola3 todos los elementos
comunes de la cola1 y la cola2, la condicin de parada de la recursividad ser cuando
hayamos tratado todos los elementos de una de las dos colas (n1 < 0 || n2 < 0).
El diseo propuesto requiere el empleo de instrucciones externas al algoritmo
recursivo: Previamente es necesario desencolar-encolar de ambas colas para disponer de los
valores iniciales de elem1 y elem2.
As pues la cabecera del mtodo recursivo quedara:
static void mezclaAndR2 (Cola cola1, Cola cola2, Cola cola3, int elem1, int elem2,
int n1 , int n2)

Fase de ida:
Se comparan los elementos procedentes de ambas colas. Tanto elem1 como elem2
proceden de operaciones de desencolar-encolar ejecutadas en instancias anteriores por
lo que se proceder a encolar el menor de ellos (o uno cualquiera si son iguales) en
cola3 (como cola3 debe tener sus elementos ordenados en el mismo sentido que cola1 y
cola2, los elementos se encolarn en cola3 antes de realizar la correspondiente llamada
recursiva).
Se desencola-encola de las cola/s pertinentes si es posible (n1 |2 > 0), pasando as
elem1|2 a la instancia siguiente.
Fase de transicin:
Si una de las colas tiene elementos por tratar y la otra no, se llama al mtodo auxiliar
reOrdenar, que recoloca la cola que tiene elementos pendientes por tratar.
Fase de vuelta:
No hay que realizar ningn tratamiento especfico, ni es necesario realizar la operacin
de encolar dado que ya se ha hecho en la fase de ida (mecanismo desencolar-encolar).
A continuacin se muestra el algoritmo que resuelve el problema

84 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

static void reOrdenar (Cola cola, int n) throws ColaVacia {


int elem;
if (n > 0) {
elem = cola.desencolar ();
cola.encolar (elem);
reOrdenar (cola, n-1);
}
}
static void mezclaAndR2 (Cola cola1, Cola cola2, Cola cola3, int elem1, int elem2,
int n1 , int n2) throws ColaVacia {
if ((n1 >= 0) && (n2 >= 0))
if (elem1 < elem2) {
if (n1 > 0) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
}
mezclaAndR2 (cola1,cola2,cola3,elem1,elem2,n1-1,n2);
}
else if (elem1 > elem2) {
if (n2 > 0) {
elem2 = cola2.desencolar ();
cola2.encolar (elem2);
}
mezclaAndR2 (cola1,cola2,cola3,elem1,elem2,n1,n2-1);
}
else {
cola3.encolar (elem1);
if (n1 > 0) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
}
if (n2 > 0) {
elem2 = cola2.desencolar();
cola2.encolar (elem2);
}
mezclaAndR2 (cola1,cola2,cola3,elem1,elem2,n1-1,n2-1);
}
else {
if (n1 >= 0)
reOrdenar (cola1, n1 );
else if (n2 >= 0)
reOrdenar (cola2, n2);
}
}
static void mezclarColasAndR2 (Cola cola1,Cola cola2, Cola cola3) throws ColaVacia {
int elem1, elem2, n1 , n2;
n1 = cola1.numElemCola ();
n2 = cola2.numElemCola ();
if ((n1 > 0) && (n2 > 0)) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
elem2 = cola2.desencolar ();
cola2.encolar (elem2);
mezclaAndR2(cola1, cola2, cola3, elem1, elem2, n1-1, n2-1);
}
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 85

2.4.6.6.2.2.3.6.6.2. Ejemplo 2. Realizar la unin de dos colas.


En este ejemplo se parte de dos colas (cola1 y cola2) ordenadas ascendentemente
desde el punto de salida hacia el de llegada, y se desea obtener una nueva cola (cola3) con
todos los elementos de ambas (sin repeticiones), ordenados en el mismo sentido. La Figura
2.12. muestra un ejemplo.
2

77

cola1
2

2
5

77

cola3

cola2

Figura 2.12.Ejemplo de obtencin de una cola con los elementos de otras dos
Para resolver este ejemplo, utilizaremos la tcnica recursiva conociendo el nmero de
elementos. Como en este caso queremos introducir en la cola3 todos los elementos de la
cola1 y la cola2 sin repeticiones, la condicin de parada de la recursividad ser cuando
hayamos tratado todos los elementos de ambas colas (n1 < 0 && n2 < 0).
El diseo propuesto requiere el empleo de instrucciones externas al algoritmo
recursivo: Previamente es necesario desencolar y encolar de ambas colas para disponer de
los valores iniciales de elem1 y elem2.
As pues la cabecera del mtodo recursivo quedara:
Static void MezclaOrR2 (Cola cola1, Cola cola2,Cola cola3,int elem1,int elem2,int
n1 ,int n2);

El tratamiento implica bsicamente, lo siguiente:


Fase de ida:
Comparar los elementos procedentes de ambas colas. Tanto elem1 como elem2
proceden de operaciones de desencolar ejecutadas en instancias anteriores por lo que se
proceder a encolar el menor de ellos (o uno cualquiera de los dos si son iguales) en
cola3 (como cola3 debe tener sus elementos ordenados en el mismo sentido que las
cola1 y cola2, los elementos se encolan en cola3 antes de realizar la llamada recursiva).
Se desencola-encola de las colas pertinentes si es posible (n1 |2 > 0), pasando as
elem1|2 a la instancia siguiente.
Fase de transicin:
Si una de las colas tiene elementos por tratar y la otra no, se copia el elemento
pendiente de tratar en cola3 y se llama al mtodo auxiliar copiarcola, que copia lo que
queda de una de las colas en cola3.

86 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

Fase de vuelta:
No hay que realizar ningn tratamiento especfico, ni es necesario realizar la operacin
de encolar dado que ya se ha hecho en la fase de ida (mecanismo desencolar-encolar).
A continuacin se muestra el algoritmo que resuelve el problema.
static void copiarcola (Cola origen, Cola destino, int n) throws ColaVacia {
int elem;
if (n > 0) {
elem = origen.desencolar ();
destino.encolar (elem);
origen.encolar (elem);
copiarcola (origen, destino, n-1);
}

}
static void mezclaOrR2 (Cola cola1, Cola cola2, Cola cola3, int elem1, int elem2,
int n1 , int n2) throws ColaVacia {
if ((n1 >= 0) && (n2 >= 0))
if (elem1 < elem2) {
cola3.encolar (elem1);
if (n1 >0) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
}
mezclaOrR2 (cola1, cola2, cola3, elem1, elem2, n1-1, n2);
}
else if (elem1 > elem2) {
cola3.encolar (elem2);
if (n2 > 0) {
elem2 = cola2.desencolar ();
cola2.encolar (elem2);
}
mezclaOrR2 (cola1,cola2,cola3,elem1,elem2,n1,n2-1);
}
else {
cola3.encolar (elem1);
if (n1 > 0) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
}
if (n2 > 0) {
elem2 = cola2.desencolar ();
cola2.encolar (elem2);
}
mezclaOrR2(cola1,cola2,cola3,elem1,elem2,n1-1,n2-1);
}
else {
if (n1 >= 0) {
cola3.encolar (elem1);
copiarcola (cola1, cola3, n1);
}
else if (n2 >= 0) {
cola3.encolar (elem2);
copiarcola (cola2, cola3, n2);
}
}
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 87

static void mezclarColaOrR2 (Cola cola1, Cola cola2, Cola cola3) throws ColaVacia {
int elem1 = -9999, elem2 = -9999, n1 , n2;
n1 = cola1.numElemCola ();
n2 = cola2.numElemCola ();
if (n1 > 0) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
}
if (n2 > 0) {
elem2 = cola2.desencolar ();
cola2.encolar (elem2);
}
mezclaOrR2 (cola1, cola2, cola3, elem1, elem2, n1-1, n2-1);
}

2.4.6.7.2.3.6.7.

Mezcla de colas con terminacin anticipada.

Dadas dos colas (cola1 y cola2) ordenadas ascendentemente desde el inicio hasta el
final (sin elementos repetidos en cada una de las colas), se desea realizar un mtodo
booleano que indique si los elementos de la cola2 estn contenidos en la cola1.
Este ejemplo se puede resolver a partir de la consideracin de mezcla de colas en la
modalidad AND (termina cuando se ha procesado por completo cualquiera de las colas).
En este caso se produce una situacin de terminacin anticipada cuando, en su caso,
aparece por primera vez un elemento de cola2 que no est en cola1 (elem1 > elem2). El
proceso debe terminar y el mtodo devuelve false.
En caso contrario (terminacin pesimista) pueden darse las siguientes
circunstancias:
Se ha procesado completa cola1 (n1 < 0) pero cola2 no (n2 >=0). An quedan
elementos en cola2 por tanto no cumple la condicin de estar contenida en cola1, y
en consecuencia el resultado ser false.
Se ha procesado cola2 completa (n2 < 0) y pero cola1 no (n1 >= 0). Por tanto, cola2
estar contenida en cola1, y en consecuencia el resultado ser true.
Se ha conseguido llegar a la terminacin pesimista (se han procesado completamente
ambas colas: n1 < 0 y n2 < 0). Por lo tanto, cola2 est contenida en cola1 y el mtodo
devolver el valor true.
El diseo propuesto requiere el empleo de instrucciones externas al algoritmo
recursivo: Previamente es necesario desencolar-encolar de ambas colas para disponer de los
valores iniciales de elem1 y elem2.
Aplicando a este caso el tratamiento genrico, se trata de:
Fase de ida.
Desencolar-encolar si procede (en funcin del valor de los argumentos n1 y n2). Se
pueden dar los siguientes casos:

88 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

o elem1 > elem2. Es imposible que la cola2 se encuentre contenida en la cola1, por lo
que habr que recolocar ambas colas, utilizando el mtodo auxiliar ordenada, y
devolver false como resultado.
o elem1 < elem2. Hay que desencolar de cola1 en la siguiente ejecucin. La llamada
recursiva se hace con n1-1 y n2.
o elem1 == elem. La llamada recursiva se hace con n1-1 y n2-1.
Fase de transicin
Si queda algn elemento pendiente de tratar en alguna de las colas (n1 >= 0 o
n2 >= 0), habr que reordenar la cola que corresponda (con ordenada) y asignar el
resultado correspondiente al mtodo.
Fase de vuelta:
No hay que realizar ningn tratamiento especfico, ni es necesario realizar la operacin
de encolar dado que ya se ha hecho en la fase de ida (mecanismo desencolar-encolar).
El cdigo se muestra a continuacin:
static void ordenar (Cola cola, int n) throws ColaVacia {
int elem;
if (n > 0) {
elem = cola.desencolar ();
cola.encolar (elem);
ordenar (cola, n-1);
}
}
static boolean contenida (Cola cola1, Cola cola2, int elem1, int elem2, int n1 , int
n2) throws ColaVacia {
boolean resul;
if ((n1 >= 0) && (n2 >= 0))
if (elem1 < elem2) {
if (n1 > 0) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
}
resul = contenida (cola1, cola2, elem1, elem2, n1-1, n2);
}
else if (elem1 > elem2) {
ordenar (cola1, n1 );
ordenar (cola2, n2);
resul = false;
}
else {
if (n1 > 0) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
}
if (n2 > 0) {
elem2 = cola2.desencolar ();
cola2.encolar (elem2);
}
resul = contenida (cola1, cola2, elem1, elem2,n1-1,n2-1);
}
else {
if (n1 >= 0) {
ordenar (cola1, n1);
resul = true;
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 89

else if (n2 >= 0) {


ordenar (cola2, n2);
resul = false;
}
else resul = true;

}
return resul;

}
static boolean estaContenida (Cola cola1, Cola cola2) throws ColaVacia {
int elem1, elem2, n1, n2;
boolean resul;
n1 = cola1.numElemCola ();
n2 = cola2.numElemCola ();
if ((n1 > 0) && (n2 > 0)) {
elem1 = cola1.desencolar ();
cola1.encolar (elem1);
elem2 = cola2.desencolar ();
cola2.encolar (elem2);
resul = contenida (cola1, cola2, elem1, elem2, n1-1, n2-1);
}
else if (n2 > 0)
resul = false;
else resul = true;
return resul;
}

90 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

2.5.2.4. CONSTRUCCIN DE UN TAD A PARTIR DE OTRO.


Se pretende en este apartado mostrar la posibilidad de construir nuevos TAD a partir
de otros. A continuacin se muestra un ejemplo en el que se construye a partir del TAD pila
un TAD vector de nmeros enteros tad_Vector. Dispone de las siguientes funcionalidades:
o crearVector (). Prepara un nuevo vector de tipo TSVector. El mtodo solicita al
usuario el nmero de elementos y los inicializa con 0.
o escribirVector (pos, dato). Mtodo que lleva a la posicin pos del vector el valor
indicado en el argumento dato. Deber de verificar que se trata de una posicin
vlida (no superior al nmero de elementos indicado en la creacin del vector).
o leerVector (pos). Mtodo que devuelve el contenido de la posicin pos del vector.
Deber verificar, igualmente, que se trata de una posicin vlida.
o tamao ().Mtodo que devuelve el tamao actual del vector.
En virtud de la caracterstica de ocultamiento, propia de la los TADs, el usuario
imagina y trabaja con algo que se comporta como un vector sin saber cmo est construido
(Figura 2.13.).

0
10

1
0

2
30

3
0

4
50

Figura 2.13. TAD Vector.


Primero definiramos un interfaz en el que indicaramos cuales son las operaciones
del nuevo TAD:
import java.io.IOException;
public interface Vector {
void crearVector () throws NumberFormatException, IOException;
int leerVector (int posicion) throws PilaVacia;
void escribirVector (int posicion, int contenido) throws PilaVacia;
int tamao ();
}

A continuacin, se construyen las operaciones. Se utilizan dos mtodos auxiliares


(leer y escribir), que se definen como privados.

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 91

import java.io.IOException;
import java.io.InputStreamReader;
import tadPila.Pila;
import tadPila.TadPila;
import tadPila.PilaVacia;
public class TadVector implements Vector {
Pila vector;
public int tamano;
public void crearVector () throws NumberFormatException, IOException {
int i;
BufferedReader linea = new BufferedReader(new InputStreamReader
(System.in));
Pila auxv = new TadPila ();

System.out.println ("Numero de elementos: ");


tamano = Integer.parseInt(linea.readLine());
for (i = 1; i <= tamano; i++)
auxv.apilar (0);
vector = auxv;

private int leer (int i, int posicion, Pila vector) throws PilaVacia {
int resul, elem;

if (i < posicion) {
elem = vector.desapilar ();
resul = leer (i+1, posicion, vector);
vector.apilar (elem);
}
else {
elem = vector.desapilar ();
resul = elem;
vector.apilar (elem);
}
return resul;

public int leerVector (int posicion) throws PilaVacia {


int resul, i;

if ((posicion > tamano) || (posicion <= 0)) {


System.out.println ("Error. Valor fuera de rango");
resul = 99999;
}
else {
i = 0;
resul = leer (i+1, posicion, vector);
}
return resul;

private void escribir (int i, int posicion, int contenido, Pila vector) throws
PilaVacia {
int elem;

if (i < posicion) {
elem = vector.desapilar ();
escribir (i+1, posicion, contenido, vector);
vector.apilar (elem);
}
else {
elem = vector.desapilar ();
vector.apilar (contenido);
}

92 TIPOS ABSTRACTOS DE DATOS

ESTRUCTURAS DE DATOS

public void escribirVector (int posicion, int contenido) throws PilaVacia {


if ((posicion > tamano) || (posicion < 0))
System.out.println ("Error. Indice fuera de rango");
else
escribir (1, posicion, contenido, vector);
}
public int tamao () {
return tamano;
}
}

El siguiente es un sencillo programa que prueba el nuevo TAD.


import java.io.IOException;
public class PruebaVector {
public static void main(String[] args) throws NumberFormatException,
IOException, Pilavacia {
Vector v = new TadVector ();
int d,i;
v.crearVector ();
v.escribirVector (5, 77);
v.escribirVector (3, 87);
v.escribirVector (8, 97);
for (i = 1; i <= v.tamao (); i++) {
d = v.leerVector (i);
System.out.println ("El dato de la posicion "+i+" es: "+d);
}
}
}

ESTRUCTURAS DE DATOS

TIPOS ABSTRACTOS DE DATOS 93

TEMA 2 ...................................................................................................................... 51
2.1.

Concepto ....................................................................................................... 51

2.2.

TAD Pila de nmeros enteros. ..................................................................... 53

2.2.1.

Concepto. .............................................................................................. 53

2.2.2.

Modelo grfico ..................................................................................... 53

2.2.3.

Especificaciones ................................................................................... 53

2.2.4.

Interfaz del TAD Pila. .......................................................................... 54

2.2.5.

Prueba del TAD pila. ............................................................................ 55

2.2.6.

Algoritmos bsicos con pilas. ............................................................... 56

2.3.

TAD Cola de nmeros enteros. .................................................................... 70

2.3.1.

Concepto. .............................................................................................. 70

2.3.2.

Modelo grfico. .................................................................................... 70

2.3.3.

Especificaciones ................................................................................... 70

2.3.4.

Interfaz del TAD Cola. ......................................................................... 71

2.3.5.

Prueba del TAD Cola. .......................................................................... 72

2.3.6.

Algoritmos bsicos con colas. .............................................................. 73

2.4.

Construccin de un TAD a partir de otro. .................................................... 90

ESTRUCTURAS DE DATOS

LISTAS 93

TEMA 3

Listas.
3.1. CONCEPTOS GENERALES.
Una lista es una estructura de datos lineal que se puede representar simblicamente
como un conjunto de nodos enlazados entre s.
Las listas permiten modelar diversas entidades del mundo real como por ejemplo, los
datos de los alumnos de un grupo acadmico, los datos del personal de una empresa, los
programas informticos almacenados en un disco magntico, etc.
La figura 3.1 muestra un ejemplo de lista correspondiente a los nombres y apellidos
de un conjunto de alumnos con su cdigo de matrcula.
Arias Gonzlez, Felipe

aa1253
Garca Sacedn, Manuel

ax0074

Lpez Medina, Margarita

Ramrez Heredia, Jorge

Yuste Pelez, Juan

lp1523

gb1305

Figura 3.1. Ejemplo de Lista

mj7726

94 LISTAS

ESTRUCTURAS DE DATOS

Tal vez resulte conveniente identificar a los diferentes elementos de la lista (que
normalmente estarn configurados como una estructura de registro) mediante uno de sus
campos (clave) y en su caso, se almacenar la lista respetando un criterio de ordenacin
(ascendente o descendente) respecto al campo clave.
Una definicin formal de lista es la siguiente:
Una lista es una secuencia de elementos del mismo tipo, de cada uno de los cuales
se puede decir cul es su siguiente (en caso de existir).
Existen dos criterios generales de calificacin de listas:
Por la forma de acceder a sus elementos.
o Listas densas. Cuando la estructura que contiene la lista es la que determina la
posicin del siguiente elemento. La localizacin de un elemento de la lista es la
siguiente:
Est en la posicin 1 si no existe elemento anterior.
Est en la posicin N si la localizacin del elemento anterior es (N-1).
o Listas enlazadas: La localizacin de un elemento es:
Estar en la direccin k, si es el primer elemento, siendo k conocido.
Si no es el primer elemento de la lista, estar en una direccin, j, que est
contenida en el elemento anterior.
Por la informacin utilizada para acceder a sus elementos:
o Listas ordinales. La posicin de los elementos en la estructura la determina su orden
de llegada.
o Listas calificadas. Se accede a un elemento por un valor que coincide con el de un
determinado campo, conocido como clave. Este tipo de listas se pueden clasificar a
su vez en ordenadas o no ordenadas por el campo clave.

ESTRUCTURAS DE DATOS

LISTAS 95

3.2. IMPLEMENTACIN DE LISTAS.


El concepto de lista puede implementarse en soportes informticos de diferentes
maneras.
Mediante estructuras estticas. Con toda seguridad resulta el mecanismo ms intuitivo.
Una simple matriz resuelve la idea (figura 3.2).
0
1
2
3
4

Arias Gonzlez, Felipe


Garca Sacedn, Manuel
Lpez Medina, Margarita
Ramrez Heredia, Jorge
Yuste Pelez, Juan

aa1253
ax0074
mj7726
lp1523
gb1305

Figura 3.2. Implementacin de una lista densa mediante una estructura esttica
(matriz).
El problema de esta alternativa es el derivado de las operaciones de insercin y
modificacin.
En efecto, la declaracin de una lista mediante una matriz implica conocer de
antemano el nmero (o al menos el orden de magnitud) de elementos que va a
almacenar, pudiendo darse las circunstancias de que si se declara pequeo podra
desbordarse su capacidad o, en caso contrario, declararlo desproporcionadamente
elevado provocara un decremento de eficiencia.
Otro problema asociado es el tratamiento de los elementos eliminados. Dado que
en el caso de no informar, de alguna manera, de la inexistencia de dicho elemento el
nodo previamente ocupado (y ahora no vlido) quedara como no disponible.
Adicionalmente, si se desea trabajar con listas ordenadas el algoritmo de
insercin debera alojar a los nuevos elementos en la posicin o con la referencia
adecuada.
Algunas soluciones, ms o menos ingeniosas, permiten tratar estas
circunstancias. La figura 3.3 muestra un ejemplo basado en una matriz de registros.
0 1

1 10 77 12 26 21 11 13 18
2 3 4 7 6 0 8 5 0

Figura 3.3. Ejemplo de implementacin de lista enlazada mediante una estructura


esttica (matriz).
Otra posible representacin de esta lista sera la siguiente:

96 LISTAS

ESTRUCTURAS DE DATOS

10

12

13

21

Figura 3.4. Lista enlazada correspondiente a la figura 3.3.


El tratamiento de las listas sobre matriz (tanto densas como enlazadas) se desarrollar
ms adelante, en el apartado 3.8.
Mediante estructuras dinmicas.
Sin duda se trata de la mejor alternativa para la construccin de listas. Se trata de
hacer uso de la correspondiente tecnologa que implica el uso de referencias.
La idea consiste en declarar una referencia a un nodo que ser el primero de la lista.
Cada nodo de la lista (en la memoria dinmica) contendr tanto la propia informacin del
mismo como una referencia al nodo siguiente. Se deber establecer un convenio para
identificar el final de la lista1.
La figura 3.5 muestra un ejemplo de implementacin dinmica de la lista de la figura
3.4.

Memoria esttica

Lista

10

21
1

Memoria dinmica

12
13

Figura 3.5. Implementacin de una lista mediante estructuras dinmicas.


Lo explicado hasta el momento consiste en la solucin ms sencilla: cada nodo de la
lista apunta al siguiente, con las excepciones del ltimo elemento de la lista (su
apuntador, o referencia es un valor especial, por ejemplo null, en java) y del primero,
que es apuntado por la referencia declarada en la memoria esttica. Esta tecnologa se
identifica como Listas enlazadas o unidireccionales. Existen otras posibilidades entre las
que cabe mencionar: listas bidireccionales o doblemente enlazadas, listas circulares, listas
con cabecera, listas con centinela o cualquier combinacin de ellas.

En java la referencia final tomar el valor null.

ESTRUCTURAS DE DATOS

LISTAS 97

3.3. TRATAMIENTO DE LISTAS EN JAVA


Para la utilizacin de listas es necesario definir la clase NodoLista utilizando la
siguiente sintaxis:
class NodoLista {
public int clave;
public NodoLista sig;
public NodoLista(int x, NodoLista n) {
clave = x;
sig = n;
}
}

As como la clase Lista:


public class Lista {
public Lista (String nombreLista) {
inicio = null;
nombre = nombreLista;
}
public NodoLista inicio;
public String nombre;
}

La representacin grfica de una variable de la clase Lista llamada lista1 que


contenga los elementos 10, 13 y 21 sera:
lista1

lista1

Variable
esttica

nombre

10
inicio

Clase Lista

dato

13
sig

NodoLista

dato

sig

NodoLista

Figura 3.6. Ejemplo de lista enlazada.

21

null

dato

sig

NodoLista

98 LISTAS

ESTRUCTURAS DE DATOS

3.4. ALGORITMOS BSICOS CON LISTAS2.


Los algoritmos que implican el recorrido, parcial o total, de la lista pueden
implementarse tanto de forma recursiva como iterativa.
3.4.1. Recorrido completo.
El recorrido de una lista de manera recursiva se puede realizar por medio de un
mtodo static invocado desde un programa que utilice la clase Lista. Esto implica utilizar el
tipo NodoLista para avanzar por la lista y ver el contenido del campo clave. Merecen una
consideracin especial:
La llamada inicial donde lista es el valor de la variable esttica.
El final del recorrido, que se alcanza cuando la lista est vaca.
En el siguiente mtodo esttico (escribirLista), se recorre una lista (nodoLista)
mostrando en la pantalla el contenido de sus campos clave. Se utiliza un mtodo de llamada
(escribirListaCompleta), que recibe como argumento un objeto de la clase Lista (lista):
static void escribirLista (NodoLista nodolista) {
if (nodoLista != null) {
System.out.print (nodoLista.clave + " ");
escribirLista (nodoLista.sig);
}
else System.out.println (" FIN");
}
static void escribirListaCompleta (Lista lista) {
if (lista != null) {
System.out.print (lista.nombre + : );
escribirLista (lista.inicio);
}
else System.out.println ("Lista vaca);
}

Si se aplica el algoritmo anterior a la lista de la figura 3.6, el resultado sera la


secuencia:
lista1: 10 13 21 FIN.

La ejecucin de escribirListaCompleta implicara una llamada inicial al mtodo


escribirLista, pasando como argumento lista.inicio (la referencia al nodo de clave 10) y 3
llamadas recursivas al mtodo escribirLista:

Los siguientes algoritmos se han desarrollado considerando implementaciones de la lista como estructuras
dinmicas. A efectos de la realizacin de prcticas se utiliza la sintaxis del lenguaje java.

ESTRUCTURAS DE DATOS

LISTAS 99

En la primera llamada recursiva, nodoLista es el contenido del campo sig del nodo de
clave 10. Es decir, una referencia al nodo de clave 13.
En la segunda, nodoLista es el contenido del campo sig del nodo de clave 13. Es decir,
una referencia al nodo de clave 21.
En la tercera, nodoLista es el contenido del campo sig del nodo de clave 21, es decir,
null. Cuando se ejecuta esta tercera llamada se cumple la condicin de finalizacin y, en
consecuencia, se inicia el proceso de vuelta. Ahora nodoLista toma sucesivamente los
valores:
o Referencia al nodo de clave 21 (campo sig del nodo de clave 13).
o Referencia al nodo de clave 13 (campo sig del nodo de clave 10).
o Referencia al nodo de clave 10 (el primer elemento de la lista).
El recorrido de una lista es una operacin necesaria en los algoritmos de eliminacin
y, en muchos casos, tambin en los de insercin. La condicin de finalizacin pesimista
consiste en alcanzar el final de la lista (nodoLista == null). No obstante, normalmente se
produce una terminacin anticipada que se implementa no realizando nuevas llamadas
recursivas.
En los siguientes apartados se van a presentar los diferentes tipos de listas, sobre las
cuales se explican algunos algoritmos bsicos: inicializacin, bsqueda, insercin y
eliminacin.

100 LISTAS

ESTRUCTURAS DE DATOS

3.5. LISTAS ORDINALES.


En las listas ordinales el orden dentro de la estructura lo establece la llegada a la
misma. A diferencia de las listas calificadas, en este tipo de listas no existe ningn elemento
que identifique el nodo, y por lo tanto, los valores se pueden repetir. El criterio de insercin
resulta especfico en cada caso (se podra insertar por el principio, o bien por el final).
Veremos a continuacin dos ejemplos de listas ordinales que ya hemos tratado como TADs:
las pilas y las colas.
3.5.1. Pilas.
Como se ha visto en el tema 2, una pila es una agrupacin de elementos de
determinada naturaleza o tipo (datos de personas, nmeros, procesos informticos,
automviles, etc.) entre los que existe definida una relacin de orden (estructura de
datos). En funcin del tiempo, algunos elementos de dicha naturaleza pueden llegar a la
pila o salir de ella (operaciones / acciones). En consecuencia el estado de la pila vara.
En una pila (comportamiento LIFO -Last In First Out-) se establece el criterio de
ordenacin en sentido inverso al orden de llegada. As pues, el ltimo elemento que lleg al
conjunto ser el primero en salir del mismo, y as sucesivamente. Las figuras 2.12 y 2.13
ilustran respectivamente el concepto de pila de nmeros enteros y su implementacin
mediante una lista dinmica.
desapilar

apilar
5
4
1
7
2

cima

fondo

Figura 3.7. Modelo grfico de Pila


5

null

Pila
apilar

desapilar

Figura 3.8. Implementacin de una pila mediante una lista dinmica


La estructura de datos de la pila y el constructor utilizado sera:

ESTRUCTURAS DE DATOS

LISTAS 101

package tadPila;
//En esta clase se define el nodo:
class NodoPila {
// Constructor
NodoPila (int elemento, NodoPila n) {
dato = elemento;
siguiente = n;
}
// Atributos accesibles desde otras rutinas del paquete
int dato;
NodoPila siguiente;
}

Y la interfaz utilizada sera la que se ha visto en el tema de TADs:


package tadPila;
import java.io.*;
public interface Pila {
void inicializarPila ();
boolean pilaVacia ();
void eliminarPila ();
int cima () throws PilaVacia;
void apilar (int x);
int desapilar () throws PilaVacia;
void decapitar () throws PilaVacia;
void imprimirPila ();
void leerPila () throws NumberFormatException, IOException;
int numElemPila ();
}

A continuacin se muestra la clase TadPila, con el constructor y los algoritmos


correspondientes a las operaciones pilaVacia, inicializarPila, as como apilar y desapilar
que operan al principio de la lista por razones de eficiencia.
package tadPila;
public class TadPila implements Pila {
protected NodoPila pila;
public TadPila () {
pila = null;
}
public void inicializarPila() {
pila = null;
}
public boolean pilaVacia () {
return pila == null;
}
public void apilar (int dato) {
NodoPila aux = new NodoPila (dato,pila);
pila = aux;
}

102 LISTAS

ESTRUCTURAS DE DATOS

public int desapilar () throws PilaVacia {


int resultado;
if (pilaVacia ())
throw new PilaVacia ("Desapilar: La pila est vaca");
resultado = pila.dato;
pila = pila.siguiente;
return resultado;
}
}

3.5.2. Colas.
Como se ha visto en el tema 2, una cola es una agrupacin de elementos de
determinada naturaleza o tipo (datos de personas, nmeros, procesos informticos,
automviles, etc.) entre los que existe definida una relacin de orden. En funcin del
tiempo, pueden llegar a la cola o salir de ella algunos elementos de dicha naturaleza
(operaciones/acciones). En consecuencia el estado de la cola vara.
En una cola (comportamiento FIFO -First In First Out-) se respeta como criterio de
ordenacin el momento de la llegada: el primero de la cola, ser el que primero lleg a ella
y, en consecuencia, el primero que saldr, y as sucesivamente. Las figuras 2.14 y 2.15
ilustran respectivamente el concepto de cola de nmeros enteros y su implementacin
mediante una lista dinmica.
desencolar

2 1 4 3 5

encolar

Figura 3.9. Modelo grfico de Cola


Debido al comportamiento de la cola, la forma ms eficiente de implementarla por
medio de listas enlazadas, es utilizando dos referencias: una al principio de la cola (por
donde desencolaremos) y otra al final (por donde encolaremos):
principio

fin

Cola
2

desencolar

null

encolar

Figura 3.10. Implementacin de una cola mediante una lista dinmica


La estructura de datos de la cola y el constructor sera:

ESTRUCTURAS DE DATOS

LISTAS 103

package tadCola;
//En esta clase se define el nodo:
class NodoCola {
// Constructor
NodoCola (int elemento, NodoCola n) {
dato = elemento;
siguiente = n;
}
// Atributos accesibles desde otras rutinas del paquete
int dato;
NodoCola siguiente;
}

Y la interfaz utilizada sera la que se ha visto en el tema de TADs:


package tadCola;
import java.io.IOException;
public interface Cola {
void inicializarCola ();
boolean colaVacia ();
void eliminarCola ();
int primero ()throws ColaVacia;
void encolar (int x);
int desencolar () throws ColaVacia
void quitarPrimero ()throws ColaVacia;
void mostrarEstadoCola ();
void imprimirCola ();
void leerCola () throws NumberFormatException, IOException;
int numElemCola ();
void invertirCola ()throws ColaVacia;
}

A continuacin se muestrala clase TadCola, que contiene el constructor, as como los


algoritmos correspondientes a las operaciones inicializarCola, colaVacia, desencolar
(opera al principio de la lista verificando la situacin de excepcin de recibir una cola
vaca), encolar (opera al final de la lista)3, numElemCola e invertirCola.

104 LISTAS

ESTRUCTURAS DE DATOS

public class TadCola implements Cola {


private NodoCola principio;
private NodoCola fin;
public TadCola () {
principio = null;
fin = null;
}
public void inicializarCola () {
principio = null;
fin = null;
}
public boolean colaVacia () {
return principio==null;
}
public void encolar (int x) {
NodoCola aux = new NodoCola(x,null);
if (principio == null) {
principio = aux;
fin = aux;
}
else {
fin.siguiente = aux;
fin = aux;
}
}
public int desencolar () throws ColaVacia {
int resultado;
if (colaVacia ())
throw new ColaVacia ("Desencolar: La cola est vaca");
resultado = principio.dato;
principio = principio.siguiente;
if (principio == null)
fin = null;
return resultado;
}
public int numElemCola () {
NodoCola aux;
int resul;
aux = principio;
resul = 0;
while (aux != null) {
++resul;
aux = aux.siguiente;
}
return resul;
}
public void invertirCola ()throws ColaVacia {
int elem;
if (!colaVacia ()) {
elem = desencolar ();
invertirCola ();
encolar (elem);
}
}
}
3

Se propone otra solucin basada en una lista circular (apartado 3.7.1.) en la que la referencia a la lista es la
del ltimo nodo. De esta forma tambin se tiene acceso a ambos extremos (cola: ltimo y cola.sig: primero).

ESTRUCTURAS DE DATOS

LISTAS 105

3.6. LISTAS CALIFICADAS.


Se caracterizan por la existencia de un campo que identifica de manera unvoca cada
uno de los nodos de la lista (identificativo o clave); lgicamente dicho valor debe ser nico.
Consideraremos dos casos: listas calificadas ordenadas y listas calificadas no ordenadas, en
funcin de que la organizacin fsica de la lista se establezca siguiendo un criterio de
ordenacin (ascendente o descendente) o no de la clave. En ambos casos, no se permitir la
insercin de elementos con la clave repetida.
A lo largo de los siguientes apartados utilizaremos una lista calificada, cuyos nodos
estarn compuestos por una clave y la referencia al siguiente nodo, utilizando la siguiente
estructura:
public class NodoLista {
public int clave;
public NodoLista sig;
public NodoLista (int x, NodoLista n) {
clave = x;
sig = n;
}
}

La clase Lista incluir las variables miembro y el constructor que aparecen a


continuacin:
public class Lista {
public NodoLista inicio;
public String nombre;
public Lista (String nombreLista) {
inicio = null;
nombre = nombreLista;
}
}

3.6.1. Listas calificadas no ordenadas.


En los siguientes apartados, utilizaremos una lista calificada, con sus elementos sin
ordenar. La inicializacin y el recorrido completo seran similares a los vistos en el
apartado 3.2. (algoritmos bsicos con listas), cambiando los tipos de datos. En los
siguientes apartados, veremos como se buscan, aaden y borran elementos de una lista
calificada no ordenada.

106 LISTAS

ESTRUCTURAS DE DATOS

3.6.1.1. Bsqueda.
Se trata de localizar una clave de valor determinado (pasado como argumento) sin
entrar en consideraciones de qu se va a hacer con ella. En cualquier caso este tipo de
algoritmos no tienen efecto alguno sobre la estructura (no se modifica el nmero de nodos).
En principio la condicin de finalizacin se consigue al llegar al final de la lista
(inicio == null). Se produce una terminacin anticipada en caso de encontrar la clave
buscada.
El siguiente algoritmo es un ejemplo de mtodo booleano de objeto (busqueda) que
recibiendo como argumento un dato (elem) devuelve true en caso de encontrar el elemento,
y false si el elemento no est en la lista.
static boolean busqueda (NodoLista nodoLista, int x) {
boolean resul = false;
if (nodoLista != null)
if (nodoLista.clave == x)
resul = true;
else resul = busqueda (nodoLista.sig, x);
return resul;
}
public boolean busqueda (int x) {
return busqueda (inicio, x);
}

3.6.1.2.Insercin.
El efecto de este tipo de algoritmos es incrementar el nmero de nodos. Para crear un
nuevo nodo es necesario conocer tanto la naturaleza de la estructura utilizada (esttica o
dinmica) como los recursos del lenguaje de programacin empleado. Por ejemplo, en el
caso de las estructuras dinmicas en java el mtodo es el siguiente:
NodoLista aux = new NodoLista (dato, null);

De esta forma se crea un nuevo nodo (apuntado por aux) cuya clave contiene el dato a
insertar. De momento el nuevo nodo no est enlazado en la lista. El punto de insercin
depender del criterio que se establezca.
Lo ms sencillo y eficiente es insertar el nuevo nodo al principio de la lista. El
resultado ser el ilustrado en la figura 3.7.

ESTRUCTURAS DE DATOS

inicio

aux

LISTAS 107

Situacin inicial
10

13

Creacin del nuevo nodo


aux

11

21

null

null

Insercin al principio
11

inicio

10

13

21

inicio

Situacin final
11

10

13

21

null

Figura 3.11. Insercin al principio de la lista.


El siguiente algoritmo muestra el mtodo completo4:
public void insertarAlPrincipio (int x) {
NodoLista aux = new NodoLista (x, inicio);
inicio = aux;
}

El problema que tendra este algoritmo es que, dado que no comprueba si el elemento
existe o no en la lista, podr insertar claves repetidas. Para evitarlo, se podra realizar
primero una bsqueda, y si la clave no estuviese ya en la lista (resultado del mtodo
busqueda (dato) == true), se insertara. Otra posibilidad es realizar la insercin al final de
la lista, comprobando si existe ya algn nodo con la clave buscada, utilizando un algoritmo
recursivo.
Se utilizar un mtodo de objeto de la clase Lista, que invoca a un mtodo static al
que se le pasa como argumento un NodoLista, y devuelve como resultado un NodoLista. En
el mtodo static vamos avanzando por la lista, haciendo las llamadas recursivas con
nodoLista.sig. Si llegamos hasta el final de la lista sin localizar la clave buscada, se crea un
nuevo nodo (aux), y lo devuelve como resultado.
Para enlazar correctamente el nodo, a la vuelta de la recursividad, se hace que
nodoLista.sig apunte al mismo sitio que nos ha devuelto la llamada anterior como resultado.
static NodoLista insertarAlFinal (NodoLista nodoLista, int dato) {
NodoLista aux = nodoLista;
if (nodoLista!= null)
if (nodoLista.clave != dato)
nodoLista.sig = insertarAlFinal (nodoLista.sig, dato);
else System.out.println ("la clave ya existe");
else aux = new NodoLista (dato, nodoLista);
return aux;
}
public void insertar (int dato) {
inicio = insertarAlFinal (inicio, dato);
}

Se contempla la situacin excepcional de que la lista se encuentre inicialmente vaca.

108 LISTAS

ESTRUCTURAS DE DATOS

La figura 3.8 ilustra el proceso seguido tras alcanzar la condicin de finalizacin


(nodoLista == null)5.

inicio

Situacin inicial
10

13

21

null

Insercin al final
aux

11

inicio

10

13

21

inicio

Situacin final
10

13

21

null

11

null

Figura 3.12. Insercin al final de la lista.


Si se desea realizar la insercin en una lista calificada no ordenada de manera
iterativa, se podra utilizar el algoritmo que aparece a continuacin. Obsrvese que, adems
de la variable aux, es necesario utilizar otros dos referencias, actual y anterior para recorrer
la lista y verificar que el elemento no existe. Se utiliza, adems, la variable seguir, de tipo
boolean, que inicialmente es true y lo seguir siendo a no ser que encontremos la clave en
la lista.
public void insertarIterativo (int dato) {
NodoLista anterior, actual, aux;
boolean seguir;
anterior = inicio;
actual = inicio;
seguir = true;
while ((actual != null) && seguir)
if (actual.clave == dato)
seguir= false;
else {
anterior = actual;
actual = actual.sig;
}
if (seguir) {
aux = new NodoLista (dato, null);
if (inicio == null)
inicio = aux;
else anterior.sig = aux;
}
else System.out.println ("Error. Elemento repetido");
}

Al realizar la insercin de manera iterativa, es necesario distinguir dos casos posibles:


cuando insertamos un nodo en una lista vaca (tenemos que cambiar la referencia inicio), y
cuando la lista ya tena elementos previamente (insertamos al final, como nodo siguiente
del elemento apuntado por anterior)
5

Obsrvese que el algoritmo es vlido cuando se recibe una lista inicialmente vaca.

ESTRUCTURAS DE DATOS

LISTAS 109

3.6.1.3.Eliminacin.
Este tipo de algoritmos reduce el nmero de nodos de la estructura. En la mayora de
los lenguajes de programacin deber prestarse atencin especial a liberar el espacio de
memoria de los nodos eliminados para posibles usos posteriores.6
Se procede a recorrer la lista comparando las sucesivas claves con el argumento
recibido (dato).
La condicin de finalizacin pesimista sera alcanzar el final de la lista, lo que
significara que no se habra encontrado la clave a eliminar. No obstante lo normal es que se
produzca una terminacin anticipada en el momento en que se encuentra la clave a
eliminar.
La figura 3.9 ilustra grficamente el mecanismo de eliminacin.
Situacin inicial y localizacin de la clave a borrar
aux
inicio

10

23

21

null

21

null

Cambio de los enlaces


aux
inicio

10

23

inicio

Situacin final
10

21

null

Figura 3.13. Eliminacin de un nodo de la lista


El algoritmo utilizado es:
static NodoLista eliminar (NodoLista nodoLista, int dato) {
NodoLista resul = nodoLista;
if (nodoLista!= null)
if (nodoLista.clave != dato)
nodoLista.sig = eliminar (nodoLista.sig, dato);
else resul = nodoLista.sig;
else System.out.println ("la clave no existe");
return resul;
}
public void eliminar (int dato) {
inicio = eliminar (inicio, dato);
}

En java no es necesario porque se realiza automticamente, pero en los ejemplos se liberar la memoria no
utilizada.

110 LISTAS

ESTRUCTURAS DE DATOS

3.6.1.4. Listas Reorganizables.


Se denomina as al tipo de listas en las que la posicin de los elementos va variando
en funcin de los accesos que se hacen sobre la estructura. Cada vez que se accede a un
nodo de la lista, ste pasa a convertirse en el primer elemento de la misma, desplazando al
elemento que antes era el primero.
Normalmente este tipo de listas se emplean con la intencin de mejorar la eficiencia
de un proceso. Por ejemplo, cuando se piensa que existe cierta probabilidad de acceder con
mayor frecuencia a determinadas claves, suele proporcionar buenos resultados reubicar los
nodos a los que se haya accedido recientemente al principio de la lista, mientras que los que
se consultan poco se desplazan a las posiciones finales de la lista. Lgicamente este
tratamiento no tiene sentido para listas calificadas ordenadas.
Los mtodos de insercin o borrado, seran los correspondientes a las listas enlazadas
calificadas no ordenadas.
A continuacin, se realizar la bsqueda de un elemento de la lista (pasamos la clave
que estamos buscando, y recibimos como resultado true si hemos encontrado dicha clave, y
false en caso contrario). El algoritmo se desarrolla en dos mtodos (reorganizar, y
buscarYSaltar), que utilizan la variable miembro aux de tipo NodoLista. El mtodo
reorganizar recibe como argumento la clave, y se la pasa a buscarYSaltar. Dicho mtodo
devolver como resultado una referencia (aux) con la direccin del nodo de clave cuyo
valor se busca (o null en caso de que no exista) a la vez que separa dicho nodo de la lista.
Una vez conocida dicha referencia, si es distinta de null, se procede a insertar al principio
de la lista el nodo apuntado por aux.
La figura 3.23 muestra una ilustracin del proceso en el que se accede al nodo de
clave 13.

inicio

Situacin inicial
10

15

13

21

14

null

inicio

Despus de buscarYSaltar
10
15

13

21

14

null

inicio

Situacin final
13

15

21

14

null

aux

10

Figura 3.14. Lista reorganizable.


El mtodo de bsqueda, con tratamiento recursivo, es equivalente al explicado para
las listas calificadas no ordenadas, si bien incluye la variante de desenlazar de la lista el
nodo cuya clave se busca para pasar su referencia al mdulo principal.

ESTRUCTURAS DE DATOS

En el cdigo que aparece a continuacin se muestra la solucin descrita.


static NodoLista buscarYSaltar (NodoLista nodoLista, int dato) {
NodoLista resul;
if (nodoLista == null)
resul = null;
else if (nodoLista.clave != dato) {
resul = buscarYSaltar (nodoLista.sig, dato);
if ((nodoLista.sig == resul) && (resul != null))
nodoLista.sig = resul.sig;
}
else resul = nodoLista;
return resul;
}
static boolean reorganizar (Lista lista, int dato) {
boolean resul = false;
NodoLista aux;
aux = buscarYSaltar (lista.inicio, dato);
if (aux != null) {
aux.sig = lista.inicio;
lista.inicio= aux;
resul = true;
}
return resul;
}

LISTAS 111

112 LISTAS

ESTRUCTURAS DE DATOS

3.6.2. Listas calificadas ordenadas.


En los siguientes apartados, utilizaremos una lista calificada, con sus elementos
ordenados ascendentemente. Los algoritmos de bsqueda, insercin y eliminacin en listas
calificadas ordenadas presentan las siguientes diferencias con respecto al caso de las listas
no ordenadas:
Los algoritmos de bsqueda y eliminacin deben considerar como posibilidad adicional
de finalizacin anticipada la situacin de encontrar una clave superior al dato pasado
como argumento. Esto se interpreta como una situacin de error consistente en que la
clave buscada no existe.
En el caso de insercin la condicin de finalizacin pesimista (nodoLista == null) se
entiende como que la clave a insertar es de valor superior a la ltima de la lista.
Tambin es vlido para insertar en una lista vaca. La terminacin anticipada se produce
cuando, como consecuencia de la exploracin de la lista se encuentra una clave de valor
igual o superior al de la clave a insertar.
A continuacin se muestran ejemplos de dichos algoritmos.
3.6.2.1. Bsqueda.
Se trata de localizar una clave de valor determinado (pasado como argumento) sin
entrar en consideraciones de qu se va a hacer con ella. En cualquier caso este tipo de
algoritmos no tienen efecto alguno sobre la estructura (no se modifica el nmero de nodos).
En principio la condicin de finalizacin se consigue al llegar al final de la lista
(inicio == null). Se produce una terminacin anticipada en caso de encontrar la clave
buscada o una de valor superior a la misma.
El siguiente algoritmo es un mtodo de objeto (busqueda) que, recibiendo como
argumento un dato (elem) devuelve true en caso de encontrarlo, y false si el elemento no
est en la lista. Llamamos a un mtodo recursivo static (tambin llamado busqueda) al que
le pasamos como argumento un NodoLista (inicio).
static boolean busqueda (NodoLista nodoLista, int x) {
boolean resul = false;
if (nodoLista != null)
if (nodoLista.clave == x)
resul = true;
else if (nodoLista.clave < x)
resul = busqueda (nodoLista.sig, x);
return resul;
}
public boolean busqueda (int x) {
return busqueda (inicio, x);
}

ESTRUCTURAS DE DATOS

LISTAS 113

3.6.2.2. Insercin.
En este caso se realiza un tratamiento recursivo recorriendo la lista y terminando
anticipadamente en cuanto se accede a la primera clave de valor superior a la que se desea
insertar (la condicin de terminacin general sera llegar al final de la lista, es decir,
nodoLista == null). El nuevo nodo se deber insertar en la posicin anterior al nodo actual.
La Figura 3.10 ilustra el proceso de insercin de un nodo de clave 11 en una lista en el
momento de la finalizacin anticipada.

Inicio

Inicio

Situacin inicial
10

13

Creacin del nuevo nodo


aux

11

10

13

Insercin en su lugar
aux

21

null

21

null

11

Inicio

10

13

21

Inicio

Situacin final
10

11

13

21

null

Figura 3.15. Insercin en una lista ordenada.


A continuacin se muestra un algoritmo de insercin7.
static NodoLista insertar (NodoLista nodoLista, int dato) {
NodoLista resul = nodoLista;
if (nodoLista != null)
if (nodoLista.clave < dato)
nodoLista.sig = insertar (nodoLista.sig, dato);
else if (nodoLista.clave > dato)
resul = new NodoLista (dato, nodoLista);
else System.out.println ("la clave ya existe");
else resul = new NodoLista (dato, nodoLista);
return resul;
}
public void insertar (int dato) {
inicio = insertar (inicio, dato);
}

Como en el caso de las listas calificadas no ordenadas, el mtodo esttico insertar, si


aade el nuevo nodo, lo devuelve como resultado, para que a la vuelta se enlace el campo
sig del nodo anterior con el nodo nuevo. Si todava no hemos llegado a la posicin de
insercin, devolvemos como resultado el nodo actual.
7

Obsrvese que el algoritmo es vlido para insertar un elemento nuevo delante del primero, detrs del ltimo
y en una lista vaca. As mismo, contempla el caso de intentar insertar una clave ya existente. Este ltimo caso
tambin produce una situacin de terminacin anticipada.

114 LISTAS

ESTRUCTURAS DE DATOS

Para realizar la insercin se podra utilizar tambin un algoritmo iterativo. Como en el


caso de las listas calificadas no ordenadas, tendramos que utilizar las variables auxiliares
actual y anterior para recorrer la lista. Adems, utilizaramos la variable encontrado, de
tipo boolean, para salir del bucle de localizacin del hueco.
public void insertarIterativo (int dato) {
NodoLista anterior, actual, aux;
boolean encontrado;
anterior = inicio;
actual = inicio;
encontrado = false;
while ((actual != null) && !encontrado)
if (actual.clave >= dato)
encontrado = true;
else {
anterior = actual;
actual = actual.sig;
}
if (actual == null) {
aux = new NodoLista (dato, null);
if (inicio == null)
inicio = aux;
else anterior.sig = aux;
}
else if (encontrado && (actual.clave > dato)) {
aux = new NodoLista (dato, actual);
if (inicio == actual)
inicio = aux;
else anterior.sig = aux;
}
else System.out.println ("Error. Elemento repetido");
}

Al realizar la insercin en una lista calificada ordenada de manera iterativa, es


necesario distinguir dos posibles casos: cuando insertamos un nodo al principio de la lista
(tenemos que cambiar la referencia inicio), y cuando la lista ya tena elementos previamente
(insertamos o bien por la parte central de la lista, o bien al final, poniendo el nodo nuevo
aux entre los elementos apuntados por anterior y actual)

ESTRUCTURAS DE DATOS

LISTAS 115

3.6.2.3. Eliminacin.
Para borrar un elemento, se procede a recorrer la lista comparando las sucesivas
claves con el argumento pasado. La condicin de finalizacin pesimista se alcanza al
llegar al final de la lista, lo que significa que no se ha encontrado ni la clave a eliminar ni
ninguna mayor. No obstante lo normal es que se produzca una terminacin anticipada en el
momento en que se encuentra o bien la clave a eliminar, o bien una clave de valor mayor
que la buscada.
La figura 3.11 ilustra grficamente el mecanismo de eliminacin.
Situacin inicial y localizacin de la clave a borrar
aux
inicio

10

13

21

null

21

null

Cambio de los enlaces


aux
inicio

10

13

inicio

Situacin final
10

21

null

Figura 3.16. Eliminacin de un nodo de una lista calificada ordenada.


El algoritmo utilizado es:
static NodoLista eliminar (NodoLista nodoLista, int dato) {
NodoLista resul = nodoLista;
if (nodoLista != null)
if (nodoLista.clave < dato)
nodoLista.sig = eliminar (nodoLista.sig, dato);
else if (nodoLista.clave > dato)
System.out.println ("la clave no existe");
else resul = nodoLista.sig;
else System.out.println ("la clave no existe");
return resul;
}
public void eliminar (int dato) {
inicio = eliminar (inicio, dato);
}

116 LISTAS

ESTRUCTURAS DE DATOS

3.6.2.4. Mezcla de listas.


Se tratan a continuacin dos casos en los que partiendo de dos listas (lista1 y lista2)
ordenadas de forma ascendente, se trata de obtener una nueva lista (lista3), con elementos
de ambas. En el primer ejemplo crearemos lista3 con los elementos comunes de lista1 y
lista2 (interseccin) en tanto que en el segundo lista3 contendr todos los elementos (sin
repeticiones) de lista1 y lista2 (unin). En ambos casos realizaremos mtodos static
(pasando como argumentos objetos de la clase NodoLista).
3.6.2.4.1. Obtencin de una lista con los elementos comunes de otras dos.
La condicin de finalizacin se produce cuando se ha terminado de explorar alguna
de las listas (nodoLista1 == null || nodoLista2 == null).
El avance recursivo por nodoLista1, nodoLista2 o ambas tiene lugar como
consecuencia del resultado de comparar los elementos actuales de ambas listas. En caso de
que sean iguales, adems, se produce la insercin del correspondiente nodo de lista3.
A continuacin se muestra el cdigo.
static NodoLista mezclaAnd(NodoLista nodo1, NodoLista nodo2, NodoLista nodo3){
NodoLista resul;
if (nodo1 != null && nodo2 != null)
if (nodo1.clave < nodo2.clave)
resul = mezclaAnd (nodo1.sig, nodo2, nodo3);
else if (nodo1.clave > nodo2.clave)
resul = mezclaAnd (nodo1, nodo2.sig, nodo3);
else {
nodo3 = mezclaAnd (nodo1.sig, nodo2.sig, nodo3);
resul = new NodoLista (nodo1.clave, nodo3);
}
else resul = null;
return resul;
}
static void mezclaAnd (Lista lista1, Lista lista2, Lista lista3) {
lista3.inicio = mezclaAnd (lista1.inicio, lista2.inicio, lista3.inicio);
}

3.6.2.4.2. Obtencin de una lista con todos los elementos de otras dos.
En este caso hay que recorrer ambas listas en su totalidad. La condicin de
finalizacin ser ((nodoLista1 == null) && (nodoLista2 == null)).
Si una de las listas est vaca, o la otra tiene un elemento menor, lo insertaremos en la
lista resultado y avanzaremos por esa lista. En caso de que ambas listas tengan el mismo
elemento, lo insertaremos y avanzaremos por ambas a la vez.
El proceso presenta tres situaciones (1+2) en funcin de que haya elementos en las dos
listas o en una s y en otra no:

ESTRUCTURAS DE DATOS

LISTAS 117

o Cuando queden elementos en ambas listas (1 caso), compararemos su valor para


insertar el menor de los elementos y avanzar por la lista con el elemento menor
(o por ambas si son iguales),
o Cuando slo queden elementos en una de las dos listas (2 casos), utilizaremos
un mtodo auxiliar (copiar), que copiar lo que quede en dicha lista sobre la
lista resultado.
El algoritmo utilizado aparece a continuacin:
static NodoLista copiar (NodoLista nodoListaO) {
NodoLista resul;
if (nodoListaO != null) {
resul = copiar (nodoListaO.sig);
resul = new NodoLista (nodoListaO.clave, resul);
}
else resul = null;
return resul;
}
static NodoLista mezclaOr (NodoLista nodoLista1, NodoLista nodoLista2,
NodoLista nodoLista3) {
NodoLista resul;
if (nodoLista1 != null && nodoLista2 != null)
if (nodoLista1.clave < nodoLista2.clave) {
nodoLista3 = mezclaOr (nodoLista1.sig, nodoLista2, nodoLista3);
resul = new NodoLista (nodoLista1.clave, nodoLista3);
}
else if (nodoLista1.clave > nodoLista2.clave) {
nodoLista3 = mezclaOr (nodoLista1, nodoLista2.sig, nodoLista3);
resul = new NodoLista (nodoLista2.clave, nodoLista3);
}
else {
nodoLista3 = mezclaOr (nodoLista1.sig, nodoLista2.sig, nodoLista3);
resul = new NodoLista (nodoLista1.clave, nodoLista3);
}
else if (nodoLista1 != null)
resul = copiar (nodoLista1);
else if (nodoLista2 != null)
resul = copiar (nodoLista2);
else resul = null;
return resul;
}
static void mezclaOr (Lista lista1, Lista lista2, Lista lista3) {
lista3.inicio = mezclaOr (lista1.inicio, lista2.inicio, lista3.inicio);
}

118 LISTAS

ESTRUCTURAS DE DATOS

3.7. OTRAS IMPLEMENTACIONES.


El concepto de lista admite diferentes implementaciones con estructuras de datos
tanto estticas como dinmicas. A continuacin se muestran, a ttulo de ejemplo, dos
posibles soluciones implementadas mediante estructuras dinmicas: listas circulares y
bidireccionales. As mismo, se plantea la tcnica de realizacin de listas con cabecera y
centinela, utilizada para las listas calificadas.
3.7.1. Listas circulares (anillos).
Se entiende por lista circular aquella en la que el ltimo elemento de la lista tiene
definido un enlace al primer elemento de la lista.
Desde un punto de vista fsico una lista circular por propia naturaleza no tiene
principio ni fin. No obstante, desde el punto de vista lgico, la referencia a la lista establece
cmo determinar la finalizacin de su recorrido tanto iterativo como recursivo.
El nodo de referencia podra ser cualquiera de los nodos de la lista. A continuacin se
muestra como ejemplo (figura 3.16) el tratamiento de una lista calificada ordenada en que
el nodo de referencia es el ltimo (la clave de valor ms alto). Este diseo facilita el acceso
inmediato a ambos extremos de la lista lo que significara un diseo eficiente, por ejemplo,
para la implementacin de colas8 realizando la operacin de encolar a continuacin del
nodo apuntado por ultimo (a la derecha en la figura) y desencolar en el extremo contrario
(siguiente nodo).
ultimo

Figura 3.17. Ejemplo de lista circular

Se sugiere al alumno como ejercicio.

ESTRUCTURAS DE DATOS

LISTAS 119

3.7.1.1.Insercin.
Si se desea realizar la insercin en una lista calificada ordenada circular, hay que
tener en cuenta tres casos diferentes:
La lista est vaca (ultimo == null): deberemos crear un nodo y enlazarlo consigo
mismo.
Vamos a insertar al final, despus del ltimo elemento: tendremos que cambiar la
referencia ultimo.
El elemento se inserta en cualquier otra posicin.
Para realizar la insercin de manera iterativa siguiendo los anteriores criterios, se
puede utilizar el siguiente algoritmo, que crea el nodo y lo enlaza en su hueco
correspondiente, una vez localizado el sitio donde se va a aadir el elemento:
public void insertar (int dato) {
NodoLista aux, actual, anterior;
if (ultimo == null) {
aux = new NodoLista (dato);
ultimo = aux;
ultimo.sig = ultimo;
}
else {
anterior = ultimo;
actual = ultimo.sig;
while ((actual.clave < dato) && (actual != ultimo)) {
anterior = actual;
actual = actual.sig;
}
if (actual.clave != dato) {
aux = new NodoLista (dato);
if ((actual != ultimo) || (actual.clave > dato)) {
aux.sig = actual;
anterior.sig = aux;
}
else if (actual.clave < dato) {
aux.sig= actual.sig;
actual.sig= aux;
ultimo = aux;
}
}
else System.out.println ("error, el elemento ya existe");
}
}

120 LISTAS

ESTRUCTURAS DE DATOS

3.7.1.2.Eliminacin.
Si se va a realizar la eliminacin de un nodo en una lista circular calificada ordenada
de manera iterativa hay que contemplar, como en el caso de la insercin, tres casos
diferentes:
La lista tiene un solo nodo, que deseamos eliminar: deberemos borrar el nodo y apuntar
la lista a null.
Vamos a borrar el nodo final, (el que estamos apuntando con ultimo): tendremos que
cambiar la referencia ultimo.
El elemento se elimina de otra posicin cualquiera.
Para realizar la eliminacin de manera iterativa siguiendo los anteriores criterios, se
puede utilizar el siguiente algoritmo:
public void eliminar (int x){
NodoLista ant, act;
if (ultimo != null) {
ant = ultimo;
act = ultimo.sig;
while (act != ultimo && act.clave < x) {
ant = act;
act = act.sig;
}
if (act.clave == x) {
ant.sig = act.sig;
if (ultimo == act)
if (ultimo != ant)
ultimo = ant;
else ultimo = null;
}
else System.out.println ("No existe el nodo de clave " + x);
}
else System.out.println ("La lista est vaca ");
}

ESTRUCTURAS DE DATOS

LISTAS 121

3.7.2. Listas bidireccionales (doblemente enlazadas).


Una lista se dice que es bidireccional o doblemente enlazada cuando, para cada uno
de los elementos que la forman, existe una referencia al elemento anterior y otra al
elemento siguiente dentro de la estructura. Gracias a esta estructura, se puede recorrer la
lista en ambos sentidos.
En la implementacin de este tipo de listas hay que considerar dos situaciones
particulares:
El elemento anterior al primero ser el elemento nulo.
El elemento siguiente al ltimo ser tambin el elemento nulo.
La figura 3.17 muestra un ejemplo de esta estructura:
inicio

null

null

Figura 3.18. Ejemplo de lista bidireccional.


Para implementar una lista de estas caractersticas mediante una estructura de datos
dinmica se puede utilizar la clase NodoLista modificada para incluir las dos referencias.
La clase NodoLista, conteniendo las variables miembro y el constructor quedara:
public class NodoLista {
int clave;
NodoLista sig, ant;
public NodoLista (int x)
clave = x;
sig = null;
ant = null;
}
}

Para realizar la inicializacin, bsqueda y recorrido completo de este tipo de listas


utilizaramos los mismos algoritmos que con las listas unidireccionales.
A continuacin se muestran ejemplos de los algoritmos de insercin (insertar) y
eliminacin (eliminar) para una lista calificada ordenada9 implementada mediante
estructuras dinmicas bidireccionales.

Se sugiere al alumno que realice los algoritmos correspondientes para listas calificadas no ordenadas.

122 LISTAS

ESTRUCTURAS DE DATOS

3.7.2.1. Insercin.
Con carcter general la insercin se produce la primera vez que se encuentra un nodo
cuya clave es de un valor superior al dato que se pretende insertar. Esto implica la
modificacin de cuatro referencias (dos en el nuevo nodo, otro en el que apunta al nodo de
clave superior y otro del nodo siguiente que apunta al actual. La figura 3.18 ilustra el
proceso (se supone que se intenta insertar un nodo de clave 5 en el momento de haber
encontrado un nodo de clave superior, 6).
inicio

null

Aux

inicio

null

null

Nuevo

null

Figura 3.19. Simulacin del mdulo de insercin delante de un nodo


El cdigo utilizado para insertar el nodo aux delante del nodo apuntado por inicio es
el siguiente:
nuevo.sig = inicio;
nuevo.ant = inicio.ant;
inicio.ant = nuevo;
inicio = nuevo;

La terminacin del proceso debe realizarse cuando se alcance el nodo cuyo campo sig
sea null (inicio.sig == null) pues en caso de progresar ms all se perdera la referencia al
nodo anterior. Si la clave que queremos insertar es superior a la ltima de la lista, habr que
insertarla en este momento, a la derecha del ltimo nodo. El cdigo siguiente indica como
implementarlo y la figura 3.19 ilustra grficamente el proceso.
nuevo.sig = null;
nuevo.ant = inicio;
inicio.sig = nuevo;

ESTRUCTURAS DE DATOS

LISTAS 123

null
2

inicio

4
null

77

aux

inicio

null

aux

77

null

Figura 3.20. Insercin al final de la lista.


Para realizar la insercin en este tipo de listas de manera iterativa, se utilizan tres
referencias auxiliares: anterior y actual para ir recorriendo la lista, y nuevo para generar el
nodo. Adems, necesitamos utilizar la variable booleana encontrado, para salir del bucle si
localizamos el hueco donde debemos insertar el nodo.
El algoritmo utilizado aparece a continuacin:
void insertar (int clave) {
NodoLista anterior, actual, nuevo;
boolean encontrado = false;
anterior = inicio;
actual = inicio;
while ((actual != null) && !encontrado)
if (actual.clave < clave) {
anterior = actual;
actual = actual.sig;
}
else encontrado = true;
if (actual == null) {
nuevo = new NodoLista (clave);
if (inicio == null)
inicio = nuevo;
else {
nuevo.ant = anterior;
anterior.sig = nuevo;
}
}

124 LISTAS

ESTRUCTURAS DE DATOS

else if (actual.clave > clave) {


nuevo = new NodoLista (clave);
nuevo.sig = actual;
nuevo.ant = actual.ant;
actual.ant = nuevo;
if (inicio != actual)
anterior.sig = nuevo;
else inicio = nuevo;
}
else System.out.println (error, la clave ya existe);
}

3.7.2.2.Eliminacin.
Al realizar la eliminacin de manera iterativa, de forma similar que en el caso de la
insercin, se utilizan dos referencias auxiliares: anterior y actual para ir recorriendo la lista.
Adems, necesitamos utilizar la variable booleana encontrado, para salir del bucle si
localizamos el nodo o una clave mayor.
Si hemos encontrado el nodo que queremos eliminar, ser necesario modificar dos
referencias:
El campo ant del nodo que sigue al que se va a eliminar deber apuntar al anterior al
eliminado (actual.sig.ant = actual.ant)
Adems ser necesario distinguir dos casos:
o si queremos borrar el primer nodo, y por tanto debemos modificar inicio,
(inicio = inicio.sig)
o o bien si queremos borrar un nodo intermedio (anterior.sig = actual.sig)
La figura 3.21 ilustra el proceso (eliminacin del nodo de clave 4).
null
inicio

null

actual

null
inicio

null

Figura 3.21. Eliminacin de un nodo intermedio

ESTRUCTURAS DE DATOS

La codificacin es la siguiente:
public void eliminar (int clave) {
NodoLista anterior, actual;
boolean encontrado = false;
anterior= inicio;
actual= inicio;
while ((actual != null) && !encontrado)
if (actual.clave < clave) {
anterior = actual;
actual = actual.sig;
}
else encontrado = true;
if (actual == null)
System.out.println ("Error, el elemento no existe");
else if (actual.clave > clave)
System.out.println ("Error, el elemento no existe");
else if (inicio == actual) {
inicio = inicio.sig;
inicio.ant = null;
}
else {
anterior.sig = actual.sig;
actual.sig.ant = anterior;
}
}

LISTAS 125

126 LISTAS

ESTRUCTURAS DE DATOS

3.7.3. Listas con cabecera ficticia y centinela.


Las tcnicas denominadas cabecera ficticia y centinela se utilizan para mejorar la
eficiencia en la codificacin iterativa de las listas calificadas..

cent
cab

67

Cabecera

Centinela

Figura 3.22. Utilizacin combinada de las tcnicas de cabecera ficticia y centinela.


En esta tcnica se utilizar la siguiente clase NodoLista:
class NodoLista {
int clave;
NodoLista sig;
NodoLista (int dato) {
clave = dato;
sig = null;
}
}

En cuanto a la clase Lista, se redefine como un registro de dos referencias a nodos de


la lista: cab (cabecera), y cent (centinela).
public class Lista {
NodoLista cab, cent;
...
}

La cabecera ficticia consiste en incorporar un nodo falso al principio de la lista.


Cualquier proceso de bsqueda se iniciar con la referencia anterior apuntando al elemento
ficticio y la referencia actual apuntando al primer elemento de la lista real (segundo nodo
de la lista fsica). Con esto se elimina la excepcin que se produce:
Cuando se quiere insertar un elemento al principio de la lista.
Cuando se va a eliminar el primer nodo de la lista.
La figura 3.23 explica esta tcnica

ESTRUCTURAS DE DATOS

LISTAS 127

Anterior

Actual

cent
cab
Cabecera

Primer elem.

Figura 3.23. Tcnica de cabecera ficticia con valores iniciales de las referencias
anterior y siguiente
El campo clave del nodo apuntado por cab a veces se utiliza para guardar algn tipo
de informacin relativa a la lista, por ejemplo el nmero de elementos de la misma.
La referencia centinela (cent) apunta a otro nodo ficticio (no contiene informacin de
la lista) que se inserta al final. La tcnica del centinela se basa, tanto en las operaciones de
bsqueda y eliminacin como en las de insercin, en copiar antes de la ejecucin del
tratamiento iterativo la clave pasada como argumento en el nodo centinela (cent.clave =
dato). Con esto se consigue simplificar la lgica de las condiciones de finalizacin dado
que siempre se va a encontrar la clave buscada. El cumplimiento de la condicin (actual
== cent), se interpreta en el sentido de que hemos llegado al final de la lista y la clave
buscada no se encuentra realmente en la lista.
Combinando las dos tcnicas anteriores se consigue una gran mejora de rendimiento
en la localizacin de informacin de una lista calificada as como una simplificacin de la
lgica. El consumo adicional de espacio (necesario para alojar los nodos cab y cent) suele
quedar compensado por las ventajas anteriormente expuestas.
3.7.3.1. Creacin.
Para crear una lista con cabecera y centinela (constructor vaco), primero crearemos
los dos nodos ficticios y despus pondremos el centinela como siguiente de la cabecera:
Lista () {
cab = new NodoLista (0);
cent = new NodoLista (0);
cab.sig = cent;
}

128 LISTAS

ESTRUCTURAS DE DATOS

3.7.3.2.Recorrido completo.
Si lo que deseamos hacer es recorrer completamente una lista, como por ejemplo,
para escribir todas las claves que contiene, tendremos que tener en cuenta que las claves
contenidas en la cabecera y el centinela no forman parte de la lista real.
void imprimirLista () {
NodoLista actual;
actual = cab.sig;
while (actual != cent) {
System.out.print (actual.clave + " ");
actual = actual.sig;
}
System.out.println (" FIN");
}

3.7.3.3. Bsqueda.
Para realizar una bsqueda de una clave en la lista con cabecera y centinela,
utilizaremos las referencias auxiliares anterior (que apuntar inicialmente a la cabecera), y
actual (que al principio apuntar al primer nodo de la lista, si ya hay nodos reales en la
lista, y al centinela si est vaca), para recorrer la lista. Al finalizar el recorrido de la lista,
devolveremos true si hemos encontrado la clave buscada, y false en caso de no haberla
localizado (o bien hemos localizado una clave mayor, o bien hemos llegado al centinela).
public boolean busqueda (int dato) {
NodoLista anterior, actual;
boolean resul = false;
anterior = cab;
actual = anterior.sig;
cent.clave = dato;
while (actual.clave < dato) {
anterior = actual;
actual = actual.sig;
}
if ((actual != cent) && (actual.clave == dato))
resul = true;
return resul;
}

3.7.3.4. Insercin.
Para insertar un nuevo elemento en la lista, utilizaremos las referencias auxiliares
anterior y actual para recorrer la lista, as como aux para crear el nuevo nodo. A
continuacin, copiamos la clave buscada en el centinela, y buscamos el hueco donde
insertar el nuevo nodo. Una vez localizado el hueco (si la clave no existe), vamos a realizar
la insercin siempre de la misma forma, ya que siempre insertaremos el nodo en la parte
interior de la lista, nunca en un extremo de la misma.

ESTRUCTURAS DE DATOS

LISTAS 129

public void insertar (int dato) {


NodoLista anterior, actual, aux;
anterior = cab;
actual = anterior.sig;
cent.clave = dato;
while (actual.clave < dato) {
anterior = actual;
actual = actual.sig;
}
if ((actual.clave > dato) || (actual == cent)) {
aux = new NodoLista (dato);
aux.sig = actual;
anterior.sig = aux;
}
else System.out.println ("Error, el elemento est repetido");
}

3.7.3.5. Eliminacin.
De forma similar a lo que ocurra en la insercin, para eliminar un elemento de la
lista, utilizaremos las referencias auxiliares anterior y actual para recorrer la lista. Una vez
localizada la clave en la lista, verificaremos que no hemos llegado al centinela (en ese caso
la clave no estara en la lista), y procederemos a desenganchar el nodo correspondiente.
public void eliminar (int dato) {
NodoLista anterior, actual;
anterior = cab;
actual = anterior.sig;
cent.clave = dato;
while (actual.clave < dato) {
anterior = actual;
actual = actual.sig;
}
if ((actual == cent) || (actual.clave > dato))
System.out.println ("Error, elemento inexistente");
else anterior.sig = actual.sig;
}

130 LISTAS

ESTRUCTURAS DE DATOS

3.8. IMPLEMENTACIN DE LISTAS UTILIZANDO MATRICES


3.8.1. Listas densas.
A lo largo de los siguientes apartados veremos otra posible implementacin de
algunas de las listas analizadas hasta ahora. En particular, veremos como contruir un
ejemplo de lista ordinal (una pila), as como una lista calificada ordenada.
3.8.1.1.Lista densa ordinal
Si deseamos construir una pila utilizando una matriz, podramos utilizar la siguiente
estructura:
Ejemplo de pila con 5 elementos (y como mximo 10 elementos):
0

numNodos = 5
N = 10

Como en las pilas los elementos se insertan y eliminan por el mismo extremo,
consideraremos que el primer elemento se apilar en la posicin 0 de la matriz, el segundo
en la posicin 1, y as sucesivamente.
Utilizaremos la variable miembro numNodos, para saber cul es el ltimo nodo
apilado (numNodos 1, que sera el primero a desapilar), y si hay espacio todava para
apilar nuevos elementos.
Para implementar el TadPila con la misma interfaz que hemos visto en el tema de
Tipos Abstractos de Datos (y en la implementacin mediante una lista enlazada), podramos
utilizar las siguientes variables miembros y constructor:
public class TadPila implements Pila {
int [ ] matriz;
final int N = 10;
int numNodos;
TadPila () {
matriz = new int [N];
numNodos = 0;
}
.....
}

ESTRUCTURAS DE DATOS

LISTAS 131

Como ya se ha indicado, numNodos representara el nmero de elementos que se han


apilado hasta ahora. A su vez, la variable miembro matriz contendr los elementos de la
pila, y N el nmero mximo de elementos que puede llegar a contener.
A continuacin, se desarrollan las operaciones apilar, desapilar y pilaVacia.
Obsrvese que la operacin apilar devolver un mensaje de error si numNodos == N (la
pila est llena), y desapilar si numNodos == 0 (la pila est vaca).
Para comprobar si la pila est vaca es suficiente con verificar el valor de numNodos.
public boolean pilaVacia () {
return numNodos == 0;
}
public void apilar (int dato) {
if (numNodos < N) {
matriz [numNodos] = dato;
numNodos++;
}
else System.out.println ("la pila est llena");
}
public int desapilar () throws PilaVacia {
int resul = -9999;
if (numNodos != 0) {
numNodos--;
resul = matriz [numNodos];
}
else throw new PilaVacia ("la pila est vaca");
return resul;
}

132 LISTAS

ESTRUCTURAS DE DATOS

3.8.1.2. Lista densa calificada ordenada.


Para construir una lista calificada ordenada utilizando una matriz, podemos plantear
las siguientes variables miembro y constructor (para una lista densa):
public class Lista {
int [ ] matriz;
final int N = 10;
int numNodos;
Lista () {
matriz = new int [N];
numNodos = 0;
}
...
}

Un posible ejemplo de lista densa calificada en la que se han insertado cinco


elementos sera:
0

numNodos = 5
N = 10

A continuacin desarrollaremos los mtodos de objeto de la clase Lista.


3.8.1.2.1. Bsqueda.
Por ejemplo, para realizar la bsqueda de una clave, recorreremos la matriz hasta
encontrar una clave mayor o igual que la buscada, o bien llegar hasta el final de la lista. Se
desarrollan dos mtodos: busqueda (int dato), que si la lista no est vaca, invoca a otro
auxiliar recursivo privado (busqueda (int i, int dato) que busca el elemento por la matriz:
public boolean busqueda (int dato) {
boolean resul = false;
if (numNodos != 0)
resul = busqueda (0, dato);
return resul;
}
private boolean busqueda (int i, int dato) {
boolean resul = false;
if (i < numNodos)
if (matriz[i] < dato)
resul = busqueda (i+1, dato);
else if (matriz [i] == dato)
resul = true;
return resul;
}

ESTRUCTURAS DE DATOS

LISTAS 133

3.8.1.2.2. Insercin.
Para realizar la insercin en una lista densa calificada ordenada, primero se
comprueba si hay espacio para el nuevo nodo con el mtodo insertar (int dato)
(comprobando si numNodos < N), y en caso afirmativo, se utiliza un mtodo auxiliar
esttico privado (insertar (int i, int dato)), que recorre la lista hasta localizar la posicin en
la que se debe insertar un nuevo elemento.
public void insertar (int dato) {
if (numNodos < N)
insertar (0, dato);
else System.out.println ("la lista est llena");
}
private void insertar (int i, int dato) {
if (i == numNodos) {
matriz [numNodos] = dato;
numNodos++;
}
else if (matriz[i] < dato)
insertar (i+1, dato);
else if (matriz [i] > dato) {
for (int j = numNodos; j > i; j--)
matriz [j] = matriz [j-1];
matriz [i] = dato;
numNodos++;
}
else System.out.println ("la clave ya existe");
}

Por ejemplo, si tenemos originalmente la siguiente lista ordenada:


0

numNodos = 5

Si fusemos a insertar un elemento mayor que el ltimo, tan solo lo colocaramos en


la posicin numNodos, e incrementaramos el valor de dicha variable:
0

12

numNodos = 6

Sin embargo, si queremos insertar un elemento por la parte central de la lista (por
ejemplo, el 5), desplazaremos todos los elementos a partir de la posicin 3 una posicin a la
derecha e incrementaremos el valor de numNodos:
0

12

numNodos = 7

134 LISTAS

ESTRUCTURAS DE DATOS

3.8.1.2.3. Eliminacin.
Para realizar la eliminacin en una lista densa calificada ordenada, primero se
comprueba si hay algn elemento con el mtodo eliminar (int dato) (comprobando si
numNodos > 0), y en caso afirmativo, se utiliza un mtodo auxiliar esttico privado
(eliminar (int i, int dato)), que recorre la lista hasta localizar la posicin en la que se debe
eliminar el elemento.
public void eliminar (int dato) {
if (numNodos != 0)
eliminar (0, dato);
else System.out.println ("la lista est vaca");
}
private void eliminar (int i, int dato) {
if (i < numNodos)
if (matriz[i] < dato)
eliminar (i+1, dato);
else if (matriz [i] > dato)
System.out.println ("la clave no existe");
else {
for (int j = i; j < numNodos-1; j++)
matriz [j] = matriz [j+1];
numNodos--;
}
}

Si partimos del ejemplo anterior:


0

12

numNodos = 7

Si queremos eliminar el ltimo elemento (12), tan solo sera necesario cambiar el
valor de numNodos:
0

12

numNodos = 6

Sin embargo, si deseamos eliminar un elemento situado por la parte central de la lista
(por ejemplo, el 4), desplazaramos hacia la izquierda todos los elementos a partir de la
posicin 3 y decrementaramos el valor de numNodos.
0

12

numNodos = 5

Obsrvese que en ninguno de los dos casos el ltimo elemento en realidad no se


elimina, pero como se ha modificado numNodos (ahora su valor es 5), las posiciones 5 y 6
no se tendrn en cuenta a menos que se inserte un nuevo elemento.

ESTRUCTURAS DE DATOS

LISTAS 135

3.8.1.2.4. Ejemplos de recorrido completo.


Por ejemplo, utilizando la anterior estructura, podramos realizar un recorrido
completo (escribir el contenido de una lista densa) con el siguiente mtodo esttico:
static void escribirLista (Lista lista) {
for (int i = 0; i < lista.numNodos; i++)
System.out.print (lista.matriz [i] + "
System.out.println (FIN);
}

");

En el siguiente ejemplo, recorremos completa la lista sumando los elementos que


contiene:
static int sumaElementos (Lista lista) {
int resul = 0;
for (int i = 0; i <lista.numNodos; i++)
resul = resul + lista.matriz [i] ;
return resul;
}

136 LISTAS

ESTRUCTURAS DE DATOS

3.8.2. Lista enlazada sobre matriz


Al principio del tema vimos el siguiente ejemplo basado en una matriz de
registros.
0 1

clave
sig

1 10 77 12 26 21 11 13 18
2 3 4 7 6 0 8 5 0

Figura 3.24. Ejemplo de implementacin de lista enlazada mediante una estructura


esttica (matriz).
Su interpretacin es la siguiente:
o Las claves de la lista se representan en el primero de los campos.
o El primer elemento del vector (ndice 0) es especial. Su primer campo hace
referencia al primer nodo de la lista (el que ocupa el primer campo de la
posicin 1, es decir 10) y el segundo la posicin del primer hueco (ndice 2
cuyo contenido 77 es irrelevante).
o El segundo campo de cada nodo indica la posicin (ndice) del nodo que le
sigue, salvo que se trate de un cero que indicara el final de la lista de nodos.
o El segundo campo de cada hueco indicara la posicin del siguiente hueco, salvo
que se trate de un cero que indicara el final de la lista de huecos.
As pues, la estructura definida en la figura 3.24., permitira almacenar hasta 8
elementos en la lista (ndices 1 a 8). En la situacin actual almacenara la secuencia de
datos de la figura 3.4: 10, 12, 13 y 21 (ndices 1, 3, 7 y 5, respectivamente).
10

12

13

21

Figura 3.25. Lista enlazada correspondiente a la figura 3.24.


Y an quedara espacio para almacenar otros 4 datos (el primer espacio disponible es
el nodo de ndice 2 y el ltimo el de ndice 8).
Para poder utilizar una matriz para implementar una lista calificada ordenada,
pordemos utilizar la estructura y constructor de la pgina siguiente.

ESTRUCTURAS DE DATOS

LISTAS 137

class NodoLista {
int clave, sig;
NodoLista () {
clave = 0;
sig = 0;
}
}
public class Lista {
final int NULL = 0, N = 9;
NodoLista [] matriz;
Lista () {
int i;
matriz = new NodoLista [N];
for (i = 0; i < N-1; i++) {
matriz [i] = new NodoLista ();
matriz [i].sig = i + 1;
}
matriz [i] = new NodoLista ();
}
....
}

Utilizaremos la clase NodoLista para incluir los campos clave y sig. La clase Lista
contendr las constantes N (con valor 9, que representa el nmero mximo de nodos de la
lista + 1) y NULL (con valor 0, que representa la posicin vaca, equivalente a null en las
listas enlazadas); adems incluye la variable miembro matriz (que es un vector de
elementos de la clase NodoLista), que contiene la lista vaca.
A continuacin desarrollaremos los mtodos de objeto de la clase Lista.
3.8.2.1.1. Bsqueda.
Para realizar la bsqueda de una clave, recorreremos la matriz siguiendo los enlaces
hasta encontrar una clave mayor o igual que la buscada, o bien llegar hasta el final de la
lista. Se desarrollan dos mtodos: busqueda (int dato), que si la lista no est vaca, invoca a
otro auxiliar recursivo privado (buscar (int pos, int dato) pasndole como argumento la
posicin del primer elemento de la lista. El mtodo buscar se encarga de localizar si el
elemento aparece en la matriz o no:
public boolean busqueda (int dato) {
boolean resul = false;
int pos = matriz [0].clave;
if (pos != 0)
resul = buscar (pos, dato);
return resul;
}

138 LISTAS

ESTRUCTURAS DE DATOS

private boolean buscar (int pos, int dato) {


boolean resul = false;
if (matriz [pos].clave < dato) {
if (matriz [pos].sig != 0)
resul = buscar (matriz [pos].sig, dato);
}
else if (matriz [pos].clave == dato)
resul = true;
return resul;
}

3.8.2.1.2. Insercin.
Para realizar la insercin en una lista calificada ordenada enlazada sobre matriz,
primero se comprueba en el mtodo insertar (int dato):
si la lista est llena (la lista de huecos estara vaca: matriz [0].sig == NULL),
en cuyo caso se producira un mensaje de error,
o bien si la lista est vaca vaca (comprobando si matriz [0].clave != 0), en
dicho caso se inserta directamente el elemento utilizando el mtodo auxiliar
inser,
en cualquier otro caso, se llama a un mtodo auxiliar esttico privado
(insertar (int pos, int ant, int dato)), que recorre la lista hasta localizar la
posicin en la que se debe insertar un nuevo elemento (y volveramos a
llamar al mtodo auxiliar inser).
public void insertar (int dato) {
if (matriz [0].sig != NULL) {
int pos = matriz [0].clave;
if (pos != 0)
insertar (matriz [0].clave, 0, dato);
else inser (0, 0, dato);
}
else System.out.println ("lista llena");
}
private void insertar (int pos, int ant, int dato) {
if (matriz [pos].clave < dato)
if (matriz [pos].sig != 0)
insertar (matriz [pos].sig, pos, dato);
else inser (0, pos, dato);
else if (matriz [pos].clave > dato)
inser (pos, ant, dato);
else System.out.println ("la clave ya existe");
}

ESTRUCTURAS DE DATOS

LISTAS 139

private void inser (int pos, int ant, int dato) {


int nuevo = matriz [0].sig;
matriz [0].sig = matriz [nuevo].sig;
matriz [nuevo].clave = dato;
if (ant != 0) {
matriz [nuevo].sig = pos;
matriz [ant].sig = nuevo;
}
else {
int sig = matriz [0].clave;
matriz [0].clave = nuevo;
if (pos == 0)
matriz [nuevo].sig = 0;
else matriz [nuevo].sig = sig;
}
}

3.8.2.1.3. Eliminacin.
Para realizar la eliminacin de un elemento, se ha optado por hacer un mtodo
iterativo. Primero se verifica si la lista est vaca, y en caso contrario, se recorre la lista
hasta encontrar la clave buscada (desenganchando el elemento de la lista de claves, y
enlazndolo en la lista de huecos), o bien una clave mayor que la buscada (en cuyo caso se
producir un mensaje de error).
public void eliminar (int d) {
int ant, pos, posAnt = 0;
if (matriz [0].clave != NULL) {
pos = matriz [0].clave;
ant = matriz [pos].clave;
while (ant < d) {
posAnt = pos;
pos = matriz [pos].sig;
ant = matriz [pos].clave;
}
if (ant == d) {
if (pos == matriz [0].clave)
matriz [0].clave = matriz [pos].sig;
else matriz [posAnt].sig = matriz[pos].sig;
matriz [pos].sig = matriz [0].sig;
matriz [0].sig = pos;
}
else System.out.println ("la clave no existe");
}
else System.out.println ("Error. La lista est vaca");
}

140 LISTAS

ESTRUCTURAS DE DATOS

3.8.2.1.4. Ejemplos de recorrido completo.


Por ejemplo, utilizando la anterior estructura, podramos realizar un recorrido
completo (escribir el contenido de una lista enlazada sobre matriz) con el siguiente mtodo
esttico:
static void escribirLista (Lista lista) {
int i = lista.matriz [0].clave;
while (i != 0) {
System.out.print (lista.matriz [i].clave+" ");
i = lista.matriz [i].sig;
}
System.out.println (FIN);
}

En el siguiente ejemplo, recorremos completa la lista sumando los elementos que


contiene:
static int sumaElementos (Lista lista) {
int i = lista.matriz [0].clave, resul = 0;
while (i != 0) {
resul = resul + lista.matriz [i].clave;
i = lista.matriz [i].sig;
}
return resul;
}

ESTRUCTURAS DE DATOS

LISTAS 141

3.9. UTILIZACIN DE UN TAD LISTA.


Los algoritmos explicados en las secciones anteriores implican el conocimiento
interno de la estructura de la lista. Otra opcin consiste en que el programador dispusiera de
un Tipo Abstracto de Datos del que, dada su caracterstica de ocultamiento, se desconoce
absolutamente su implementacin y, en consecuencia, solo es posible disponer de sus
funcionalidades mediante un conjunto de especificaciones. Estas deberan ser, al menos, las
siguientes:
public interface Lista {
void crearNodo ();
/*Crea un nuevo nodo en el tadLista*/
int devolverClave ();
/*Devuelve la clave contenida en el nodo del tadLista*/
NodoLista devolverSiguiente ();
/*Devuelve una referencia al siguiente del tadLista*/
void asignarClave (int dato);
/*Asigna el dato al primer nodo del TadLista*/
void asignarReferencia (NodoLista referencia);
/*Hace que el primer nodo del TadLista apunte al mismo sitio que
referencia*/
void asignarReferenciaSiguiente (NodoLista referenciaNueva);
/*Hace que el siguiente del nodo actual apunte ahora al mismo sitio que
referenciaNueva*/
void asignarNulo ();
/*Hace que el tadLista tome el valor null*/
boolean esNulo ();
/*Devuelve true si el inicio del TadLista tiene valor null; false en caso
contrario*/
boolean esIgual (NodoLista referencia);
/*Devuelve true si referencia apunta al mismo sitio que el tadLista, false
en caso contrario*/
}

A continuacin se muestra un sencillo ejemplo que permite contar el nmero de


nodos de una lista del TAD lista descrito arriba.
static int contar (TadLista lista) {
TadLista aux = new TadLista ();
int resul;
if (!lista.esNulo ()) {
aux.asignarReferencia (lista.devolverSiguiente ());
resul = 1 + contar (aux);
}
else resul = 0;
return resul;
}

142 LISTAS

ESTRUCTURAS DE DATOS

3.9.1. Implementacin del TAD Lista.


package tadLista;
public class TadLista implements Lista {
NodoLista inicio;
public TadLista () {
inicio = null;
}
public void crearNodo () {
/*Crea un nuevo nodo en el TadLista al principio de la lista*/
inicio = new NodoLista (0, inicio);
}
public int devolverClave () {
/*Devuelve la clave contenida en el nodo del tadLista*/
return inicio.clave;
}
public NodoLista devolverSiguiente () {
/*Devuelve una referencia al siguiente del TadLista*/
return inicio.sig;
}
public NodoLista devolverReferencia () {
/*Devuelve una referencia al primer nodo del TadLista*/
return inicio;
}
public void asignarClave (int dato) {
/*Asigna el dato al primer nodo del TadLista*/
inicio.clave = dato;
}
public void asignarReferencia (NodoLista referencia) {
/*Hace que el inicio del TadLista apunte al mismo sitio que referencia*/
inicio = referencia;
}
public void asignarReferenciaSiguiente (NodoLista referenciaNueva) {
/*Hace que el siguiente del nodo inicio apunte ahora al mismo sitio que
referenciaNueva*/
inicio.sig = referenciaNueva;
}
public void asignarNulo () {
/*Hace que el inicio del TadLista tome el valor null*/
inicio = null;
}
public boolean esNulo () {
/*Devuelve true si el inicio del TadLista tiene valor null; false en caso
contrario*/
return inicio == null;
}
public boolean esIgual (NodoLista referencia) {
/*Devuelve true si referencia apunta al mismo sitio que el inicio del
TadLista, false en caso contrario*/
return inicio == referencia;
}
}

ESTRUCTURAS DE DATOS

LISTAS 143

TEMA 3 ................................................................................................................................ 93
3.1.

Conceptos Generales. ............................................................................................... 93

3.2.

Implementacin de listas. ......................................................................................... 95

3.3.

Tratamiento de listas en java .................................................................................... 97

3.4.

Algoritmos bsicos con Listas. ................................................................................. 98


3.4.1.

3.5.

3.6.

3.7.

3.8.

3.9.

Recorrido completo. ............................................................................. 98

Listas ordinales. ...................................................................................................... 100


3.5.1.

Pilas. ................................................................................................... 100

3.5.2.

Colas. .................................................................................................. 102

Listas calificadas. ................................................................................................... 105


3.6.1.

Listas calificadas no ordenadas. ......................................................... 105

3.6.2.

Listas calificadas ordenadas. .............................................................. 112

Otras implementaciones. ........................................................................................ 118


3.7.1.

Listas circulares (anillos).................................................................... 118

3.7.2.

Listas bidireccionales (doblemente enlazadas). ................................. 121

3.7.3.

Listas con cabecera ficticia y centinela. ............................................. 126

IMPLEMENTACIN DE LISTAS UTILIZANDO MATRICES ........................ 130


3.8.1.

Listas densas. ...................................................................................... 130

3.8.2.

Lista enlazada sobre matriz ................................................................ 136

Utilizacin de un TAD Lista. ................................................................................. 141


3.9.1.

Implementacin del TAD Lista: ......................................................... 142

ESTRUCTURAS DE DATOS

RBOLES 143

TEMA 4.

RBOLES
4.1. CONCEPTOS GENERALES.
Un rbol es una estructura de datos ramificada (no lineal) que puede representarse
como un conjunto de nodos enlazados entre s por medio de ramas. La informacin
contenida en un nodo puede ser de cualquier tipo simple o estructura de datos.
Los rboles permiten modelar diversas entidades del mundo real tales como, por
ejemplo, el ndice de un libro, la clasificacin del reino animal, el rbol genealgico de un
apellido, etc.
La figura 4.1. muestra un ejemplo de estructura en rbol (la numeracin de los nodos
es arbitraria). Se entiende por topologa de un rbol a su representacin geomtrica.
1

10

11

12

Figura 4.1. Ejemplo de rbol.

144 RBOLES

ESTRUCTURAS DE DATOS

Una definicin formal es la siguiente:


Un rbol es una estructura de datos base que cumple una de estas dos condiciones:
Es una estructura vaca, o
Es un nodo de tipo base que tiene de 0 a N subrboles disjuntos entre s.
Al nodo base, que debe ser nico, se le denomina raz y se establece el convenio de
representarlo grficamente en la parte superior.
En un rbol se representa una relacin jerrquica a partir del nodo raz en sentido
vertical descendente, definiendo niveles1. El nivel del nodo raz es 1.
Desde la raz se puede llegar a cualquier nodo progresando por las ramas y
atravesando los sucesivos niveles estableciendo as un camino. En la figura 4.1. el nodo 7
est a nivel 3 y la secuencia de nodos 4, 8 y 11 constituye un (sub)camino.
Se dice que un nodo es antecesor de otro cuando ambos forman parte de un camino y
el primero se encuentra en un nivel superior (numeracin ms baja) al del segundo
(numeracin ms alta). En el ejemplo anterior el nodo 4 es antecesor del 11. Por el
contrario, el nodo 11 es descendiente del 4.
La relacin entre dos nodos separados de forma inmediata por una rama se denomina
padre/hijo. En el ejemplo de la figura 4.1., el nodo 5 es hijo del nodo 2 y, recprocamente,
el nodo 2 es padre del nodo 5. En un rbol un padre puede tener varios hijos pero un hijo
solo puede tener un padre.
Se denomina grado al nmero de hijos de un nodo. Por ejemplo, en la figura 4.1. el
nodo 4 tiene grado 3 y el nodo 7 tiene grado 0.
Se dice que un nodo es hoja cuando no tiene descendientes (grado 0).
Se establecen los siguientes atributos para un rbol:
Altura / profundidad / nivel: La mayor altura / profundidad / nivel de sus nodos. La
altura del rbol de la figura 4.1. es 4 (la alcanzan sus nodos 10, 11 y 12).
Amplitud / Anchura: El nmero de nodos del nivel ms poblado. En el ejemplo, 5
(nivel 3).
Grado: el mayor de los grados de los nodos. En el ejemplo, 3 (nodos 1 y 4).

Los trminos altura o profundidad son sinnimos del de nivel.

ESTRUCTURAS DE DATOS

RBOLES 145

Finalmente, indicar que se dice que un rbol es completo cuando todos sus nodos
(excepto las hojas) tienen el mismo grado y los diferentes niveles estn poblados por
completo. A veces resulta necesario completar un rbol aadindole nodos especiales. La
figura 4.2. representa el resultado de completar el rbol de la figura 4.1. (en la figura los
nodos especiales aparecen sombreados)
1

10

11

12

Figura 4.2. rbol completo de grado 3.


En un rbol completo de grado g, la amplitud del nivel n es An == gn-1, y el nmero
total de nodos del rbol es:
Nn

g
g

1
1

146 RBOLES

ESTRUCTURAS DE DATOS

4.2. RBOLES BINARIOS.


Se definen como rboles de grado 2. Esto es, cada nodo puede tener dos, uno o
ningn hijo. Al tratarse como mucho de dos hijos, cada uno de ellos puede identificarse
como hijo izquierdo o hijo derecho.
4.2.1. Implementacin fsica.
El grfico de un rbol es una representacin conceptual cuya implementacin fsica
admite diversas posibilidades condicionadas, en primer lugar, por el dispositivo de
almacenamiento del mismo (memoria principal o memoria externa). A los efectos del curso
nos ocuparemos exclusivamente de la memoria principal en donde puede optarse por dos
filosofas principales:
Estructuras de datos estticas, normalmente matrices.
Estructuras de datos dinmicas
En cualquier caso, la representacin fsica de un rbol requiere contemplar tres
componentes:
La clave (simple o compuesta).
Dos punteros, indicando las ubicaciones respectivas de los nodos hijo izquierdo e
hijo derecho2.
Ejemplo. La figura 4.3. representa un rbol binario que podra implementarse
fsicamente segn se ilustra en las figuras 4.4. (estructura esttica) 4.5. (estructura
dinmica).
15

25

20

10

45

Figura 4.3. Ejemplo de rbol binario.


Clave
Hijo izquierdo
Hijo derecho

0
15
3
1

1
20
5
2

2
5
4
*

3
25
*
*

4
45
*
*

5
10
*
*

Figura 4.4. Ejemplo de rbol binario. Implementacin esttica.

Deber establecerse un convenio para indicar la inexistencia de hijo(s).

ESTRUCTURAS DE DATOS

RBOLES 147

Puntero al rbol

raz

20

15
25

10
5

*
45

Figura 4.5. Ejemplo de rbol binario. Implementacin dinmica.


4.2.2. Algoritmos bsicos con rboles binarios3.
Para la utilizacin de rboles binarios es necesario definir las clases NodoArbol y
Arbol siguiendo la sintaxis siguiente:
public class NodoArbol {
public NodoArbol (int dato) {
clave = dato;
iz = null;
de = null;
}
public int clave;
public NodoArbol iz, de;
}
public class Arbol {
public NodoArbol raiz;
public Arbol () {
raiz = null;
}
}

4.2.2.1.Recorridos.
Se entiende por recorrido el tratamiento realizado para acceder a los diferentes nodos
de un rbol. El recorrido puede afectar a la totalidad de los nodos del rbol (recorrido
completo), por ejemplo si se desea conocer el nmero de nodos, o finalizar anticipadamente
en funcin de determinada/s circunstancia/s, por ejemplo al encontrar el valor de una clave
determinada.
En cualquier caso, el recorrido se puede realizar basndose en las siguientes
modalidades:

Los siguientes algoritmos se han desarrollado en Java considerando la implementacin del rbol por medio
de una estructura dinmica. A efectos de la realizacin de prcticas se utiliza la sintaxis del lenguaje Java.

148 RBOLES

ESTRUCTURAS DE DATOS

En profundidad. Se progresa verticalmente desde la raz hacia las hojas y, en


principio, de izquierda a derecha. Se plantean tres posibilidades atendiendo al
momento en que se procesa la clave.
o Preorden. La clave se procesa en primer lugar. Posteriormente se realizan las
llamadas recursivas (por la izquierda y por la derecha). La exploracin en
preorden del rbol de la figura 4.3.a. dara el siguiente resultado: 15, 25, 20, 10,
5, 45.
Este tipo de recorrido es el ms utilizado pues atiende a la estructura de
organizacin jerrquica del rbol. Tambin es el ms eficiente cuando se trata
de buscar claves.
En general, el algoritmo de recorrido en preorden es el siguiente:
// Escribe las claves del rbol binario de raiz a en preorden.
static void preOrden (NodoArbol arbol) {
if (arbol != null) {
System.out.print (arbol.clave + " ") ;
// Nodo
preOrden (arbol.iz);
// Izquierda
preOrden (arbol.de);
// Derecha
}
}
public void preOrden () {
preOrden (raiz);
}

o Orden central. Se desciende recursivamente por la rama izquierda. Al alcanzar


la condicin de finalizacin (arbol == null) se retorna y se procesa la clave, y a
continuacin se progresa por la rama derecha. La exploracin en orden central
del rbol de la figura 4.3.a. dara el siguiente resultado: 25, 15, 10, 20, 45, 5.
Este tipo de recorrido es especialmente til en todos aquellos procesos en los
que sea necesario recorrer los nodos de acuerdo al orden fsico de los mismos.
En general, el algoritmo de recorrido en orden central es el siguiente:
// Escribe las claves del rbol binario en orden central.
static void ordenCentral (NodoArbol arbol) {
if (arbol != null) {
ordenCentral (arbol.iz);
// Izquierda
System.out.print (arbol.clave + " ") ;
// Nodo
ordenCentral (arbol.de);
// Derecha
}
}
public void ordenCentral () {
ordenCentral (raiz);
}

ESTRUCTURAS DE DATOS

RBOLES 149

o Postorden. Se desciende recursivamente por la rama izquierda, al alcanzar el


final de dicha rama, se retorna y se desciende por la rama derecha. Cuando se
alcanza el final de la rama derecha, se procesa la clave. La exploracin en
postorden del rbol de la figura 4.3.a. dara el siguiente resultado: 25, 10, 45, 5,
20, 15.
Este recorrido es el menos utilizado. Se utiliza para liberar memoria, o bien
cuando la informacin de los niveles ms profundos del rbol afecta a la
informacin buscada.
En general, el algoritmo de recorrido en postorden es el siguiente:
// Escribe las claves del rbol binario en postorden.
static void postOrden (NodoArbol arbol) {
if (arbol != null) {
postOrden (arbol.iz);
// Izquierda
postOrden (arbol.de);
// Derecha
System.out.print (arbol.clave + " ") ;
// Nodo
}
}
public void postOrden () {
postOrden (raiz);
}

Ejemplo de algoritmo de recorrido en profundidad: desarrollar un mtodo esttico


(sumaClaves) que obtenga la suma de las claves del rbol4.
static int sumaClaves (NodoArbol arbol) {
int resul;
if (arbol != null)
resul = arbol.clave + sumaClaves (arbol.iz) + sumaClaves (arbol.de);
else resul = 0;
return resul;
}
static int sumaClaves (Arbol a) {
return sumaClaves (a.raiz);
}

Como ejercicio se propone al alumno la realizacin de un algoritmo que cuente los


nodos que tiene un rbol, utilizando uno de los tres recorridos en profundidad
propuestos.

Puede realizarse indistintamente con cualquiera de las modalidades de recorrido, en la solucin propuesta se
hace un recorrido en preorden.

150 RBOLES

ESTRUCTURAS DE DATOS

Recorrido en amplitud. Implica un acceso a las claves recorriendo cada nivel de


izquierda a derecha y descendiendo al siguiente. Por ejemplo, la exploracin en
amplitud del rbol de la figura 4.3.a. dara el siguiente resultado: 15, 25, 20, 10, 5,
45.
Para la implementacin del recorrido en amplitud se requiere la utilizacin de la
tcnica iterativa as como de la disponibilidad de una estructura de datos auxiliar: TAD cola
de nodos de rbol:
class NodoCola {
NodoArbol dato;
NodoCola siguiente;
// Constructores
NodoCola (NodoArbol elemento, NodoCola n) {
dato = elemento;
siguiente = n;
}
}

Por ejemplo, el algoritmo siguiente permite obtener un listado de las claves de un


rbol binario recorrido en amplitud:
static void listarAmplitud (NodoArbol arbol) {
NodoArbol p;
Cola c = new TadCola ();
p = arbol;
if (p != null)
c.encolar (p);
while (! c.colaVacia ()) {
p = c.desencolar ();
System.out.print (p.clave + " ");
if (p.iz != null)
c.encolar (p.iz);
if (p.de != null)
c.encolar (p.de);
}
}
public void listarAmplitud () {
listarAmplitud (raiz);
}

La idea, bsicamente, consiste en encolar en la cola de punteros las direcciones


(referencias) de los posibles hijos de cada nodo procesado (su direccin se encuentra en la
variable p).
A continuacin, en cada iteracin se extrae el primer elemento de la cola que se
corresponde con el nodo actual del rbol. Si su hijo izquierdo y/o su hijo derecho son
distintos de null se encolan, y el proceso terminar cuando la cola se encuentre vaca.

ESTRUCTURAS DE DATOS

RBOLES 151

4.2.2.2.Bsqueda.
Hasta ahora se han presentado distintos modos de recorrer el rbol completo. En
algunas ocasiones, ser necesario finalizar antes de completar el recorrido, no realizando
posteriores llamadas recursivas. En algunos casos se requiere el empleo de un argumento
adicional (pasado por referencia) que permita evitar, en su caso, la ejecucin de posteriores
llamadas recursivas.
El siguiente ejemplo implementa un mtodo (bsqueda) que recibe como argumentos
un rbol y una clave (ambos pasados por valor) y devuelve true si dicha clave se encuentra
en el rbol, y false en caso contrario.
Se procede a recorrer en principio todo el rbol. Cada vez que se alcanza un extremo
(llamada desde una hoja) se cumple la condicin arbol == null y el mtodo devuelve el
valor false. Si a lo largo del recorrido se encuentra la clave buscada, el mtodo devolver el
puntero a dicho nodo, y ya no se realizan nuevas llamadas recursivas, siendo este valor el
que se entrega al mdulo que llam a el mtodo, lo que evita el error que supondra
alcanzar la condicin de finalizacin en el extremo derecho del rbol (hijo derecho de la
ltima hoja).
Por razones de eficiencia se realiza un recorrido en preorden.
static boolean busqueda (NodoArbol arbol, int dato) {
boolean resul = false;
if (arbol != null)
if (arbol.clave == dato)
resul = true;
else {
resul = busqueda (arbol.iz, dato);
if (!resul)
resul = busqueda (arbol.de, dato);
}
return resul;
}
public boolean busqueda (int dato) {
return busqueda (raiz, dato);
}

4.2.2.3. Creacin de un rbol.


Para insertar claves en un rbol es necesario establecer previamente un criterio.
Normalmente no se permite insertar claves repetidas. En el apartado 4.3.2.1. se muestra
cmo insertar nodos en un rbol binario de bsqueda. Para los rboles binarios que no sean
de bsqueda, se puede utilizar el mtodo juntar, que recibe una clave y dos rboles (a1 y
a2), y devuelve un rbol con la clave en la raz, a1 como subrbol izquierdo, y a2 como
subrbol derecho:

152 RBOLES

ESTRUCTURAS DE DATOS

public void juntar (int dato, Arbol a1, Arbol a2) {


if (a1.raiz == a2.raiz && a1.raiz != null) {
System.out.println ("no se pueden mezclar, t1 y t2 son iguales") ;
return;
}
// Crear el nodo nuevo
raiz = new NodoArbol (dato, a1.raiz, a2.raiz) ;
// Borrar los rboles a1 y a2
if (this != a1)
a1.raiz = null;
if (this != a2)
a2.raiz = null;
}

En el siguiente ejemplo, se muestra como generar el rbol de la figura 4.6:


public static
Arbol a1 =
Arbol a3 =
Arbol a5 =
Arbol a7 =
Arbol a2 =
Arbol a4 =
Arbol a6 =

void main
new Arbol
new Arbol
new Arbol
new Arbol
new Arbol
new Arbol
new Arbol

(String [] args) {
(1) ;
(3) ;
(5) ;
(7) ;
();
();
();

a2.juntar (2, a1, a3) ;


a6.juntar (6, a5, a7) ;
a4.juntar (4, a2, a6) ;
}

Figura 4.6. Ejemplo de rbol binario.


4.2.2.4.Tratamiento de hojas.
En este tipo de algoritmos se trata de evaluar la condicin de que un nodo del rbol
sea una hoja:
(arbol.iz == null) && (arbol.de == null).

Por ejemplo, el siguiente mtodo de tipo entero (cuentaHojas) devuelve como


resultado el nmero de hojas del rbol que se le entrega como argumento.

ESTRUCTURAS DE DATOS

RBOLES 153

static int cuentaHojas (NodoArbol arbol) {


int resul = 0;
if (arbol != null)
if ((arbol.iz == null) && (arbol.de == null))
resul = 1;
else resul = cuentaHojas (arbol.iz) + cuentaHojas (arbol.de);
return resul;
}
static int cuentaHojas (Arbol a) {
return cuentaHojas (a.raiz);
}

4.2.2.5. Procesamiento con constancia del nivel.


Determinados tratamientos requieren conocer el nivel en que se encuentran los nodos
visitados, para lo cual es necesario conocer el nivel actual.
Recorrido en profundidad.
Cuando se va a realizar un tratamiento en profundidad, pasamos como argumento
(por valor) el nivel actual. En cada llamada recursiva (por la derecha o por la izquierda) se
incrementa el nivel en una unidad. La inicializacin de dicho argumento (a 1) se realiza en
la primera llamada (no recursiva).
Por ejemplo, el siguiente mtodo (clavesNiveles) recorre un rbol en preorden5
mostrando en la pantalla el valor de las diferentes claves as como el nivel en que se
encuentran.
static void clavesNiveles (NodoArbol arbol, int n) {
if (arbol != null) {
System.out.println ("Clave: " + arbol.clave + " en el nivel: " + n);
clavesNiveles (arbol.iz, n+1);
clavesNiveles (arbol.de, n+1);
}
}
static void clavesNiveles (Arbol a) {
clavesNiveles (a.raiz, 1);
}

Recorrido en amplitud.
El algoritmo explicado para el recorrido en amplitud en el apartado 3.2.2.2., permite
recorrer el rbol en amplitud, sin embargo, no se tiene constancia del nivel en que se
encuentran los nodos a los que se accede.
En caso de que esto sea necesario debera modificarse, bien la estructura de la cola
aadiendo el nivel, o bien el algoritmo visto anteriormente de manera que su estructura sea
una iteracin anidada en dos niveles.

La modalidad de recorrido es indiferente.

154 RBOLES

ESTRUCTURAS DE DATOS

Si se puede cambiar la estructura de la cola, podra utilizarse una cola con el siguiente
tipo de elementos:
TElemento (NodoArbol a, int n) {
nivel = n;
arbol = a;
}
NodoColaN (NodoArbol a, int n, NodoColaN sig) {
elem = new TElemento (a, n);
siguiente = sig;
}

Utilizando los siguientes mtodos para obtener el rbol y el nivel:


public NodoArbol obtenerArbol (TElemento e) {
return e.arbol;
}
public int obtenerNivel (TElemento e) {
return e.nivel;
}

De tal forma que al encolar el rbol completo, pondramos el nivel a 1, y


posteriormente, cada vez que se va a encolar un elemento, lo primero que haremos ser
actualizar el nivel.
static void listarAmplitudNiveles (NodoArbol arbol) {
NodoArbol p;
int n;
ColaN c = new TadColaN ();
p = arbol;
if (p != null) {
TElemento elem = new TElemento (p, 1);
c.encolar (elem);
while (! c.colaVacia ()) {
elem = c.desencolar ();
p = elem.obtenerArbol ();
n = elem.obtenerNivel ();
System.out.println ("nivel: " + n + " " + p.clave + " ");
if (p.iz != null) {
elem = new TElemento (p.iz, n+1);
c.encolar (elem);
}
if (p.de != null){
elem = new TElemento (p.de, n+1);
c.encolar (elem);
}
}
}
}
public void listarAmplitudNiveles (){
listarAmplitudNiveles (raiz);
}

Si no se puede alterar la estructura de la cola, sera necesario cambiar el algoritmo de


manera que existan dos niveles de iteracin. El nivel externo es el general (para todo el
rbol) y el nivel interno para cada nivel del rbol. Cuando se inicia el recorrido de un nivel

ESTRUCTURAS DE DATOS

RBOLES 155

(controlado por medio de la variable contador) es necesario conocer a priori su amplitud


(variable actual) y segn se va explorando dicho nivel se cuenta el nmero de hijos del
siguiente nivel (variable siguiente). Al iniciar el proceso del siguiente nivel se pasa a la
variable actual el ltimo valor encontrado en la variable siguiente.
A continuacin se muestra como ejemplo una variante del ejemplo anterior.
static void listarAmplitudNiveles (NodoArbol arbol) {
NodoArbol p;
Cola c = new TadCola ();
int actual, siguiente, contador, altura;
altura = 0;
siguiente = 1;
p = arbol;
if (p != null )
c.encolar (p);
while (!c.colaVacia()) {
actual = siguiente;
siguiente = 0;
contador = 1;
altura++;
while (contador <= actual) {
p = c.desencolar ();
System.out.println ("clave: " + p.clave + " nivel: " + altura);
contador++;
if (p.iz != null) {
c.encolar (p.iz);
siguiente++;
}
if (p.de != null) {
c.encolar (p.de);
siguiente++;
}
}
}
}
public void listarAmplitudNiveles (){
listarAmplitudNiveles (raiz);
}

156 RBOLES

ESTRUCTURAS DE DATOS

4.2.3. Ejemplo.
A continuacin se desarrolla un mtodo que intenta verificar si dos rboles son
iguales o no.
static boolean iguales (NodoArbol a, NodoArbol b) {
boolean resul ;
if ((a == null) && (b == null))
resul = true;
else if ((a == null) || (b == null))
resul = false;
else if (a.clave == b.clave)
resul = iguales(a.iz, b.iz) && iguales (a.de, b.de);
else resul = false;
return resul;
}
static boolean iguales (Arbol a1, Arbol a2) {
return iguales (a1.raiz, a2.raiz);
}

ESTRUCTURAS DE DATOS

RBOLES 157

4.3. RBOLES BINARIOS DE BSQUEDA.


Un rbol binario de bsqueda es un tipo de rbol binario en el que se verifica para
todo nodo que las claves de su subrbol izquierdo son menores que las de dicho nodo y las
claves de su subrbol derecho son mayores. La figura 4.10. muestra un ejemplo.
30

20

35

10

22

15

24

12

18

17

Figura 4.7. Ejemplo de rbol binario de bsqueda.


La utilizacin de rboles binarios de bsqueda implica en trminos generales las
siguientes consideraciones:
4.3.1. Algoritmos de consulta.
En caso de bsqueda de alguna clave el proceso resulta de mayor eficiencia que en el
caso general, dado que el encaminamiento se produce de forma dirigida (por la derecha si
arbol.clave < dato y por la izquierda si arbol.clave > dato). La condicin de finalizacin
pesimista es alcanzar un extremo del rbol (arbol == null) que en caso de alcanzarse (una
sola vez, no varias como podra suceder para rboles binarios genricos) implicara que no
se ha encontrado la clave buscada y el proceso finalizara.
La terminacin anticipada (no es necesario hacer ms llamadas recursivas) se produce
cuando se encuentra la clave buscada (arbol.clave == dato). A continuacin se muestra
como ejemplo el mtodo busqueda que devuelve un puntero al nodo que contiene la clave
coincidente con el argumento dato y null en caso de que no exista dicha clave.
static boolean busqueda (NodoArbol arbol, int dato) {
boolean resul = false;
if (arbol != null)
if (arbol.clave == dato)
resul = true;
else if (arbol.clave > dato)
resul = busqueda (arbol.iz, dato);
else resul = busqueda (arbol.de, dato);
return resul;
}
public boolean busqueda (int dato) {
return busqueda (raiz, dato);
}

158 RBOLES

ESTRUCTURAS DE DATOS

En caso de no buscar una clave determinada el recorrido del rbol binario de


bsqueda no ofrece ninguna particularidad respecto al rbol binario genrico, pudiendo
realizarse dicho recorrido en cualquier modalidad: orden central, preorden o postorden, as
como en amplitud.
No obstante, cuando se desea recuperar el conjunto de claves del rbol ordenadas, el
recorrido deber ser en orden central (de izquierda a derecha para secuencia ascendente y
de derecha a izquierda para secuencia descendente).
A continuacin se muestra como ejemplo el mtodo booleano esBusqueda que
devuelve true si el arbol binario, que se pasa como argumento, es de bsqueda y false en
caso contrario. Consiste en verificar que la secuencia de claves es ascendente, por lo que
habr que hacer un recorrido en orden central (de izquierda a derecha) y comparar el valor
de cada clave con la anterior (ant) que, debidamente inicializada, deber pasarse como
argumento en las sucesivas llamadas recursivas. Es necesario identificar el primer elemento
de la lista (no tiene elemento anterior con el que comparar la clave), con una variable
auxiliar booleana (primero), inicializada a true desde fuera de el mtodo.
static class Busqueda {
static int ant;
static boolean primero = true;
static boolean esBusqueda (NodoArbol arbol) {
boolean resul;
if (arbol == null)
resul = true;
else {
resul = esBusqueda (arbol.iz);
if (primero)
primero = false;
else if (arbol.clave <= ant)
resul = false;
if (resul) {
ant = arbol.clave;
resul = esBusqueda(arbol.de);
}
}
return resul;
}
}
static boolean esBusqueda (Arbol a) {
return Busqueda.esBusqueda(a.raiz);
}

ESTRUCTURAS DE DATOS

RBOLES 159

4.3.2. Algoritmos de modificacin.


4.3.2.1. Insercin.
Se trata de crear un nuevo nodo en la posicin que le corresponda segn el criterio de
rbol binario de bsqueda. A continuacin se muestra el algoritmo.
static NodoArbol insertar (NodoArbol arbol, int dato) {
NodoArbol resul = arbol;
if (arbol != null)
if (arbol.clave < dato)
arbol.de = insertar (arbol.de, dato);
else if (arbol.clave > dato)
arbol.iz = insertar (arbol.iz, dato);
else System.out.println ("la clave ya existe");
else resul = new NodoArbol (dato);
return resul;
}
public void insertar (int dato) {
raiz = insertar (raiz, dato);
}

4.3.2.2. Eliminacin.
La eliminacin de un nodo en un rbol binario de bsqueda implica una
reorganizacin posterior del mismo con el objeto de que una vez eliminado el nodo el rbol
mantenga su naturaleza de bsqueda. Para ello se procede de la manera siguiente:
15

10

20

12

17

13

25

18

23

Figura 4.8. rbol binario de bsqueda


Primero localizamos el nodo a eliminar. Una vez encontrado, tenemos tres posibles
casos:
1.

Tenemos que borrar un nodo hoja: procedemos a borrarlo directamente. Por ejemplo, si
en el rbol de la figura 4.11, queremos borrar la clave 13, el resultado sera la figura
4.12.
15

10

20

12

17

25

18

23

Figura 4.9. Borrado de un nodo hoja en un rbol binario de bsqueda

160 RBOLES

2.

ESTRUCTURAS DE DATOS

Hay que borrar un nodo con un nico descendiente: ascendemos a su descendiente. Por
ejemplo, si en el rbol de la figura 4.12, queremos borrar la clave 17, el resultado sera
la figura 4.13.
15

10

20

12

18

25

23

Figura 4.10. Borrado de un nodo con un solo hijo en un rbol binario de bsqueda
3.

El nodo que queremos borrar tiene dos hijos. En este caso, sustituimos la clave a borrar
por la clave del mayor de sus descendientes menores (el nodo ms a la derecha del
subrbol izquierdo), y borramos el nodo que tena anteriormente dicha clave. Para
poder hacer esto:
Nos situamos en la raz del subrbol izquierdo de dicho nodo.
Se desciende por la derecha de dicho subrbol hasta encontrar un nodo (n) sin
descendiente derecho.
Se sustituye la clave a eliminar por la del nodo n.
Se elimina el nodo n6. Por ejemplo, si se desea eliminar la clave 10 del rbol
representado en la figura 4.13., sustituiremos dicha clave por la mayor de sus
descendientes menores (el 7), y borraremos el nodo con la clave 7 (el nodo que
deberemos borrar ahora ser en este caso un nodo hoja, aunque tambin podramos
encontrarnos con un nodo con un solo descendiente). Como resultado, obtendremos
el rbol que se muestra en la figura 4.14.
15

20

12

17

25

23

Figura 4.11. Borrado de un nodo con dos hijos en un rbol binario de bsqueda.
A continuacin se muestra el cdigo del algoritmo de eliminacin.

Obsrvese que se elimina la clave deseada pero no el nodo fsico que la albergaba sino otro.

ESTRUCTURAS DE DATOS

RBOLES 161

static class Eliminar {


static NodoArbol eliminar2Hijos (NodoArbol arbol, NodoArbol p) {
NodoArbol resul;
if (arbol.de != null) {
resul = arbol;
arbol.de = eliminar2Hijos (arbol.de, p);
}
else {
p.clave = arbol.clave;
resul = arbol.iz;
}
return resul;
}
static NodoArbol eliminarElemento (NodoArbol arbol, int elem) {
NodoArbol p;
if (arbol != null)
if (arbol.clave > elem)
arbol.iz = eliminarElemento (arbol.iz, elem);
else if (arbol.clave < elem)
arbol.de = eliminarElemento (arbol.de, elem);
else {
p = arbol;
if (arbol.iz == null)
arbol= arbol.de;
else if (arbol.de == null)
arbol = arbol.iz;
else arbol.iz = eliminar2Hijos (arbol.iz, p);
}
else System.out.println ("la clave buscada no existe");
return arbol;
}
}
public void eliminar (int elem) {
raiz = Eliminar.eliminarElemento (raiz, elem);
}

4.3.3. Ejemplo.
Dado un rbol binario de bsqueda y una lista enlazada por medio de punteros,
implementar un algoritmo en Java que, recibiendo al menos un rbol y una lista ordenada
ascendentemente, determine si todos los elementos del rbol estn en la lista.
Implementaremos un mtodo booleano con el arbol y la lista como argumentos.
Como la lista est ordenada ascendentemente, y el rbol binario es de bsqueda,
realizaremos un recorrido del rbol en orden central, parando en el momento en que
detectemos algn elemento que existe en el rbol, pero no en la lista.

162 RBOLES

ESTRUCTURAS DE DATOS

static boolean estaContenido (NodoArbol arbol, NodoLista lista) {


boolean seguir, saltar;
if (arbol == null)
seguir = true;
else {
seguir = estaContenido (arbol.iz, lista);
if (seguir && (lista != null))
if (arbol.clave < lista.clave)
seguir = false;
else {
saltar = true;
while ((lista != null) && saltar)
if (arbol.clave == lista.clave)
saltar = false;
else lista = lista.sig;
if (!saltar)
seguir = estaContenido (arbol.de, lista.sig);
else seguir = false;
}
else seguir = false;
}
return seguir;
}
static boolean estaContenido (Arbol a, Lista l) {
return estaContenido (a.raiz, l.inicio);
}

ESTRUCTURAS DE DATOS

RBOLES 163

4.4. RBOL SOBRE MATRIZ.


4.4.1. Clases y constructores.
Si se desea implementar un rbol binario de bsqueda utilizando una matriz, se
pueden definir las siguientes clases NodoArbol y Arbol:
class NodoArbol {
int clave, izq, der;
NodoArbol () {
clave = 0;
izq = -1;
der = -1;
}
}
public class ArbolMatriz {
final int NULL = -1, N = 10;
NodoArbol [ ] matriz;
int numNodos;
ArbolMatriz () {
matriz = new NodoArbol [N];
for (int i = 0; i < N; i++)
matriz [i] = new NodoArbol ();
numNodos = 0;
}
...
}

Utilizando las clases anteriores, utilizaramos la siguiente matriz para guardar el


contenido del rbol de la figura 4.12.:
Clave
Hijo izquierdo
Hijo derecho

0
10
3
2

1
20
6
4

2
5
-1
-1

3
15
-1
-1

4
45
5
-1

5
15
-1
-1

6
0
-1
-1

7
0
-1
-1

8
0
-1
-1

9
0
-1
-1

10

20

15

45

25

Figura 4.12. Ejemplo de rbol binario.


La clave de la raz del rbol (10) aparece en la posicin 0 de la matriz, en el campo
clave. Su hijo izquierdo (el 5), est en la posicin 3 de la matriz (dado que matriz [0].izq
=3), mientras que el hijo derecho (15) estar en la posicin 2. Cuando un elemento es una
hoja (por ejemplo, el 15), su hijo izquierdo y su hijo derecho tendrn como valor NULL (es
decir, -1).

164 RBOLES

ESTRUCTURAS DE DATOS

4.4.1.1. Recorridos en profundidad.


A continuacin se detallan los cdigos correspondientes a los tres recorridos en
profundidad. En cualquiera de los casos, desde el mtodo de llamada se llama a uno auxiliar
recursivo, pasndole como argumento la posicin de la raz (0).
Recorrido en preorden:
En el mtodo recursivo privado preOrdenAux, se escriben las claves del rbol en
preorden: primero escribimos la clave del nodo (matriz [i].clave), luego se avanza por la
izquierda (haciendo la llamada con matriz [i].izq), y luego por la derecha (matriz [i].der).
private void preOrden (int i) {
if (i != NULL) {
System.out.print (matriz [i].clave+", ");
preOrden (matriz [i].izq);
preOrden (matriz [i].der);
}
}
public void preOrden () {
preOrden (0);
}

Recorrido en orden central:


private void ordenCentral (int i) {
if (i != NULL) {
ordenCentral (matriz [i].izq);
System.out.print (matriz [i].clave+", ");
ordenCentral (matriz [i].der);
}
}
public void ordenCentral () {
ordenCentral (0);
}

Recorrido en post-orden:
En el mtodo recursivo privado ordenCentralAux, se escriben las claves del rbol en
post orden: primero se avanza por la izquierda (haciendo la llamada con matriz [i].izq, ),
luego por la derecha (matriz [i].der) y por ltimo escribimos la clave del nodo (matriz
[i].clave).
private void postOrden (int i) {
if (i != NULL) {
postOrden (matriz [i].izq);
postOrden (matriz [i].der);
System.out.print (matriz [i].clave+", ");
}
}
public void postOrden () {
postOrden (0);
}

ESTRUCTURAS DE DATOS

RBOLES 165

4.4.1.2. Bsqueda.
Para realizar una bsqueda en un rbol binario de bsqueda sobre matriz, es necesario
comprobar primero si no hemos llegado a un nodo vaco (if (i != NULL), en cuyo caso
devolveremos false como resultado), y luego vamos avanzando por la rama
correspondiente:
si la clave es mayor que la buscada seguiremos por la izquierda (resul = busqueda
(matriz [i].izq, dato)),
si la clave es menor que la buscada seguiremos por la derecha (resul = busqueda
(matriz [i].der, dato)),
y si la clave es la que estamos buscando, pararemos devolviendo como resultado
true.
public boolean busqueda (int dato) {
return busqueda (0, dato);
}
private boolean busqueda (int i, int dato) {
boolean resul = false;
if (i != NULL)
if (matriz [i].clave > dato)
resul = busqueda (matriz [i].izq, dato);
else if (matriz [i].clave < dato)
resul = busqueda (matriz [i].der, dato);
else resul = true;
return resul;
}

166 RBOLES

ESTRUCTURAS DE DATOS

4.4.1.3. Insercin.
Para realizar la insercin en un rbol binario de bsqueda sobre matriz, es necesario
comprobar primero si todava no est lleno el rbol (comprobando la variable miembro
numNodos). Si se va a insertar en un rbol vaco, la insercin se har en el mtodo de
llamada (insertar):
public void insertar (int dato) {
if (numNodos == 0){
matriz [0].clave =dato;
numNodos++;
}
else if (numNodos < N)
insertar (0, dato);
else System.out.println ("rbol lleno");
}

Para el resto de los nodos utilizaremos el mtodo recursivo insertarAux. Para ello,
primero habr que localizar dnde deberamos insertar el nodo (realizando una bsqueda
guiada por el rbol). Una vez localizado, utilizaremos para el nuevo nodo la primera
posicin libre en la matriz (la posicin numNodos).
private int insertar (int i, int dato) {
int j = NULL;
if (i != NULL)
if (matriz [i].clave > dato) {
j = insertar (matriz [i].izq, dato);
if (j != NULL)
matriz [i].izq = j;
j = i;
}
else if (matriz [i].clave < dato) {
j = insertar (matriz [i].der, dato);
if (j != NULL)
matriz [i].der = j;
j = i;
}
else System.out.println ("la clave ya existe");
else {
j = numNodos;
matriz [j].clave = dato;
numNodos++;
}
return j;

ESTRUCTURAS DE DATOS

RBOLES 167

4.4.1.4. Eliminacin.
Para eliminar una clave, se sigue la misma estrategia que en el apartado 4.3.2.2:
El mtodo recursivo eliminar busca la clave que se desea eliminar.
Si es una hoja se elimina directamente,
Si es un nodo con un hijo se sustituye por el hijo,
Y si es un nodo con dos hijos se llama al mtodo eliminarDosHijos para
sustituir la clave del nodo por la inmediatamente anterior (bajando una vez a
la izquierda y luego todo el rato a la derecha hasta encontrar un nodo sin hijo
izquierdo).
public void eliminar (int dato) {
eliminar (0, dato);
}
private int eliminar (int i, int dato) {
int j = NULL;
if (i != NULL)
if (matriz [i].clave > dato) {
j = eliminar (matriz [i].izq, dato);
if (j != NULL)
matriz [i].izq = j;
j = i;
}
else if (matriz [i].clave < dato) {
j = eliminar (matriz [i].der, dato);
if (j != NULL)
matriz [i].der = j;
j = i;
}
else {
numNodos--;
if (matriz [i].der == NULL) {
j = matriz [i].izq;
borrarNodo (i);
}
else if (matriz [i].izq == NULL) {
j = matriz [i].der;
borrarNodo (i);
}
else {
matriz [i].izq = eliminarDosHijos (matriz [i].izq, i);
j = i;
}
}
else System.out.println ("la clave no existe");
return j;
}

168 RBOLES

ESTRUCTURAS DE DATOS

private int eliminarDosHijos (int i, int j) {


int resul = i;
if (matriz [i].der != NULL)
matriz [i].der = eliminarDosHijos (matriz [i].der, j);
else {
matriz [j].clave = matriz [i].clave;
borrarNodo (i);
resul = matriz [i].izq;
}
return resul;
}

Por ltimo, si encontramos el nodo a eliminar, en cualquiera de los casos (tenga cero,
uno o dos hijos) se utilizar un mtodo auxiliar (borrarNodo) para desplazar los elementos
que haya a continuacin del nodo eliminado.
private void borrarNodo (int i) {
for (int j = i; j < numNodos; j++) {
matriz [j].clave = matriz [j+1].clave;
matriz [j].der = matriz [j+1].der;
matriz [j].izq = matriz [j+1].izq;
}
for (int j = 0; j <= numNodos; j++) {
if (matriz [j].der >= i)
matriz [j].der = matriz [j].der -1;
if (matriz [j].izq >= i)
matriz [j].izq = matriz [j].izq -1;
}

Por ejemplo, si en el rbol de la figura 4.13., representado por la matriz que aparece a
continuacin, eliminamos el nodo 20, el resultado sera el de la figura 4.14:
Clave
Hijo izquierdo
Hijo derecho

0
10
2
1

1
20
3
4

2
5
-1
-1

3
15
-1
-1

4
45
5
-1

5
25
-1
-1

6
0
-1
-1

7
0
-1
-1

10

20

15

45

25

Figura 4.13. rbol binario antes de eliminar.

8
0
-1
-1

9
0
-1
-1

ESTRUCTURAS DE DATOS

RBOLES 169

Resultado:

Clave
Hijo izquierdo
Hijo derecho

0
10
2
1

1
15
-1
3

2
5
-1
-1

3
45
4
-1

4
25
-1
-1

5
25
-1
-1

6
0
-1
-1

7
0
-1
-1

8
0
-1
-1

9
0
-1
-1

10

15

45

25

Figura 4.14. rbol binario despus de eliminar la clave 20.


Obsrvese que, al haber desplazado los elementos de la matriz hacia la izquierda, la
clave 25 aparece repetida. De todas formas, dicho valor no se tendr en cuenta, dado que la
posicin 5 slo se alcanzar en caso de insertar una nueva clave (y se sustituir dicha clave
por su nuevo valor).

170 RBOLES

ESTRUCTURAS DE DATOS

4.5. UTILIZACIN DE UN TAD RBOL.


Los algoritmos explicados en las secciones anteriores implican el conocimiento
interno de la estructura del rbol binario. Otro tanto podra haberse hecho en caso de que el
rbol se hubiera implementado mediante cualquier otra alternativa posible.
Otra opcin consiste en que el programador dispusiera de un Tipo Abstracto de Datos
del que, dada su caracterstica de ocultamiento, se desconoce absolutamente su
implementacin y, en consecuencia, solo es posible disponer de sus funcionalidades
mediante un conjunto de especificaciones. A continuacin se muestra un ejemplo de un
TAD rbol binario de nmeros enteros positivos:
package tadArbol;
public interface Arbol {
void crearNodo ();
/*Crea un nuevo nodo en el rbol, que queda apuntando a ese nodo*/
int obtenerClave ();
/*Devuelve la clave contenida en la raiz del rbol*/
NodoArbol devolverRaiz ();
/*Devuelve una referencia a la raiz del arbol*/
NodoArbol devolverHijoIzquierdo ();
/*Devuelve el hijo izquierdo del rbol*/
NodoArbol devolverHijoDerecho ();
/*Devuelve el hijo derecho del rbol*/
void ponerClave (int Clave);
/*pone la clave pasada como argumento en la raz del rbol*/
void ponerReferencia (NodoArbol nuevoArbol);
/*Hace que el rbol apunte al mismo sitio que nuevoArbol*/
void ponerHijoIzquierdo (NodoArbol arbolAsignado);
/*Hace que el hijo izquierdo del arbol apunte ahora a arbolAsignado*/
void ponerHijoDerecho (NodoArbol arbolAsignado);
/*Hace que el hijo derecho del arbol, apunte ahora a arbolAsignado*/
void asignarNulo ();
/*Hace que el arbol tome el valor null*/
boolean esNulo ();
/*Devuelve true si el arbol tiene valor null y false en caso contrario*/
boolean iguales (NodoArbol otroArbol);
/*Devuelve true si la raz del rbol y la de otroArbol apuntan al mismo
sitio, false en caso contrario*/
}

Dado el TAD rbol anterior, se propone desarrollar un mtodo (sumaClaves) que


obtenga la suma de las claves del rbol.
static int sumaClaves (Arbol arbol) {
int resul = 0;
Arbol izq = new TadArbol (), der = new TadArbol ();
if (!arbol.esNulo()) {
izq.ponerReferencia (arbol.devolverHijoIzquierdo ());
der.ponerReferencia (arbol.devolverHijoDerecho ());
resul = arbol.obtenerClave () + sumaClaves (izq) + sumaClaves (der);
}
return resul;
}

ESTRUCTURAS DE DATOS

RBOLES 171

4.5.1. Implementacin del TAD rbol


package tadArbol;
public class TadArbol implements Arbol {
NodoArbol raiz;
public TadArbol () {
raiz = null;
}
/*Crea un nuevo nodo en el rbol, que queda apuntando a ese nodo*/
public void crearNodo () {
raiz = new NodoArbol ();
}
/*Devuelve la clave contenida en la raz del rbol */
public int obtenerClave () {
int resul = 0;
if (raiz != null)
resul = raiz.clave;
return resul;
}
/*Devuelve el hijo izquierdo del rbol*/
public NodoArbol devolverHijoIzquierdo () {
NodoArbol resul = null;
if (raiz!= null)
resul = raiz.iz;
return resul;
}
/*Devuelve el hijo derecho del rbol*/
public NodoArbol devolverHijoDerecho () {
NodoArbol resul = null;
if (raiz != null)
resul = raiz.de;
return resul;
}
/*pone la clave pasada como argumento en la raz del rbol*/
public void ponerClave (int clave) {
if (raiz != null)
raiz.clave = clave;
}
/*Hace que el rbol apunte al mismo sitio que nuevoArbol*/
public void ponerReferencia (NodoArbol nuevoArbol) {
raiz = nuevoArbol;
}
/*Hace que el hijo izquierdo del arbol apunte ahora a arbolAsignado*/
public void ponerHijoIzquierdo (NodoArbol arbolAsignado) {
if (raiz != null)
raiz.iz = arbolAsignado;
else System.out.println ("Error, el rbol est vaco");
}
/*Hace que el hijo derecho del arbol, apunte ahora a arbolAsignado*/
public void ponerHijoDerecho (NodoArbol arbolAsignado) {
if (raiz != null)
raiz.de = arbolAsignado;
else System.out.println ("Error, el rbol est vaco");
}

172 RBOLES

ESTRUCTURAS DE DATOS

/*Hace que el arbol tome el valor null*/


public void asignarNulo () {
raiz = null;
}
/*Devuelve true si la raz del arbol tiene valor null y false en caso
contrario*/
public boolean esNulo () {
return raiz == null;
}
/*Devuelve true si la raz del rbol apunta al mismo sitio que otroArbol,
false en caso contrario*/
public boolean iguales (NodoArbol otroArbol) {
return raiz == otroArbol;
}
/*Devuelve una referencia a la raiz del arbol*/
public NodoArbol devolverRaiz() {
return raiz;
}
}

ESTRUCTURAS DE DATOS

RBOLES 173

TEMA 4. ................................................................................................................... 143


4.1.

Conceptos generales. .................................................................................. 143

4.2.

rboles Binarios. ........................................................................................ 146

4.2.1.

Implementacin fsica. ....................................................................... 146

4.2.2.

Algoritmos bsicos con rboles binarios. ........................................... 147

4.2.3.

Ejemplo. ............................................................................................. 156

4.3.

rboles Binarios de Bsqueda. .................................................................. 157

4.3.1.

Algoritmos de consulta. ...................................................................... 157

4.3.2.

Algoritmos de modificacin. .............................................................. 159

4.3.3.

Ejemplo. ............................................................................................. 161

4.4.

rbol sobre matriz. ..................................................................................... 163

4.4.1.
4.5.

Clases y constructores. ....................................................................... 163

Utilizacin de un TAD rbol. ..................................................................... 170

4.5.1.

Implementacin del TAD rbol......................................................... 171

ESTRUCTURAS DE DATOS

GRAFOS 173

TEMA 5

Grafos.
5.1. DEFINICIN DE GRAFO
A menudo, cuando se observa la red de rutas areas de un pas interesa observar cmo
ir de una ciudad a otra por las rutas posibles. En consecuencia, se tiene dos conjuntos de
objetos distintos: ciudades y rutas. La Figura 5.1 muestra una manera de representar la
relacin existente entre las ciudades y las rutas, as como la distancia entre las distintas
ciudades.

BILBAO
OVIEDO
445

606

395
MADRID

531
SEVILLA

BARCELONA

622
437

538
ALICANTE

1006

534
207
MELILLA

221

MALAGA

Figura 5.1. Representacin de las conexiones entre ciudades.

174 GRAFOS

ESTRUCTURAS DE DATOS

En general, un grafo es una manera de representar relaciones que existen entre pares
de objetos. As, un grafo es un conjunto de objetos, llamados vrtices1, y relaciones entre
objetos que establecen una relacin entre pares de vrtices, representadas por aristas.
En el ejemplo anterior, el grafo de la Figura 5.1 representa las conexiones areas entre
ciudades. Los vrtices representaran las ciudades. Las aristas representan las conexiones
entre ciudades y, en este caso, se almacenan la distancia en kilmetros entre las ciudades
que une.
Definicin 1. Un grafo se define como un par G = (V, A), donde V es un conjunto
finito no vaco de vrtices y A es un conjunto de pares de vrtices de V, es decir, las aristas.
Definicin 2. Un grafo G se define como un par ordenado, G = (V, A), donde V es un
conjunto finito y A es un conjunto que consta de dos elementos de V.

La terminologa de la teora de grafos no es estndar. El concepto de vrtice tambin se referencia como


nodo. Asimismo, aristas (edges en ingls) y arcos denotan el mismo elemento. En algunos libros, sin
embargo, se establece una diferencia entre aristas (unen vrtices en un grafo no dirigido) y arcos (unen
vrtices en grafos dirigidos). En este captulo, se dar preferencia a los trminos vrtice y arista.

ESTRUCTURAS DE DATOS

GRAFOS 175

5.2. TERMINOLOGA Y CONCEPTOS


5.2.1. Grafos dirigidos y no dirigidos
Dependiendo del tipo de relacin entre los vrtices del grafo, se definen distintos tipos de
grafos. As se distinguen aristas dirigidas y no dirigidas:
Arista dirigida: es aquella que define un par ordenado de vrtices (u,v), donde el
primer vrtice u es el origen de la arista y el segundo vrtice v es el trmino (o
vrtice final). El par (u, v) (v, u).
Arista no dirigida: es aquella que define un par no ordenado de vrtices (u, v),
donde (u, v) = (v, u).
De esta forma se distinguen entre grafos dirigidos y grafos no dirigidos.
Grafo dirigido: Es aquel cuyas aristas son dirigidas. Los grafos dirigidos suelen
representar relaciones asimtricas como por ejemplo: relaciones de herencia, los
vuelos entre ciudades, etc.
Grafo no dirigido: Es aquel cuyas aristas son no dirigidas. Representan
relaciones simtricas como relaciones de hermandad y colaboracin, conexiones
de transportes, etc.
5.2.2.

Incidencia, adyacencia y grado de un vrtice

Sea un grafo G = (V, A), los vrtices u y v pertenecientes a V; y una arista (u,v)
perteneciente a A, se dice que:
Incidencia: la arista (u,v) es incidente con los vrtices u y con v.
Adyacencia: Dos vrtices u y v son adyacentes si existe una arista cuyos vrtices
sean u y v:
o El vrtice u es adyacente a v
o El vrtice v es adyacente desde u
Grado: El grado de un vrtice u es el nmero de vrtices adyacentes a u. Para un
grafo dirigido, el grado de salida de un vrtice u es el nmero de vrtices
adyacentes desde u, mientras que el grado de entrada de un vrtice u es el nmero
de vrtices adyacentes a u. La Figura 5.2 muestra los grados de los vrtices para
un grafo no dirigido y un grafo dirigido.

176 GRAFOS

ESTRUCTURAS DE DATOS

Grafo no dirigido:

Grafo dirigido:

e
c

e
c

Grado (a) = 3
Grado (b) = 3
Grado (c) = 2
Grado (d) = 4
Grado (e) = 4

GradoE (a) = 2
GradoE (b) = 3
GradoE (c) = 0
GradoE (d) = 2
GradoE (e) = 1

GradoS (a) = 1
GradoS (b) = 0
GradoS (c) = 2
GradoS (d) = 2
GradoS (e) = 3

Figura 5.2. Grado de los vrtices en un grafo no dirigido y dirigido.


5.2.3.

Grafos simples y multigrafos

Un grafo simple es aquel que no tiene aristas paralelas o mltiples que unan el mismo
par de vrtices. Un grafo que cuente con mltiples aristas entre dos vrtices se denomina
multigrafo. La Figura 5.3 muestra un ejemplo de grafo simple y de multigrafo, donde
existen aristas paralelas incidentes a los vrtices a y d, y e y d. En este caso, se dice que el
grafo tiene multiplicidad 2 (mximo de aristas paralelas entre dos vrtices).
MULTIGRAFO:

GRAFO SIMPLE:
a

e
c

Figura 5.3. Grafo simple y grafo no simple


Si asumimos un grafo simple, se observan las siguientes propiedades:
Si G es un grafo no dirigido con m vrtices, entonces

grado(v) = 2m.

v en G

Una arista (u, v) se cuenta dos veces en este sumatorio, uno


como vrtice final u y otro como vrtice final v. Entonces,
la contribucin total de las aristas a los grados de los
vrtices es dos veces el nmero de las aristas.

ESTRUCTURAS DE DATOS

GRAFOS 177

Si G es un grafo dirigido con m vrtices, entonces:


grado E(v) = grado S(v) = m

v en G
v en G
En un grafo dirigido, una arista (u,v) contribuye una unidad al grado de
salida de su origen u y una unidad al grado de entrada de su vrtice final v.
Por tanto, la contribucin total de las aristas al grado de salida de los
vrtices es igual al nmero de aristas, y similar para los grados de salida.
Sea G un grafo simple con n vrtices y m aristas, entonces:
Un arco (u,v) se cuenta dos veces en este sumatorio, uno como ENDPOINT
o como
Si G es
no dirigido:
n(n-1)/2.
u y otro
ENDPOINT
V.m
Entonces,
la contribucin total de los arcos a
los grados de los vrtices es dos veces el nmero de los arcos.
o Si G es dirigido: m n(n-1).
5.2.4.

Camino, bucle y ciclo

Un camino es una secuencia que alterna


vrtices y aristas que comienza por un

vrtice y termina en vrtice tal que cada arista es incidente a su vrtice predecesor y
sucesor. Es decir, un camino es un sucesin de vrtices de vi V: <v0, v1, v2, vk> que
cumple que:
(vi,,vi+1) A i {0 k-1}.
Se dice que este camino tiene longitud k. Es decir, el nmero de aristas de un camino o
ciclo es la longitud del camino.
Un camino es simple si cada vrtice en el camino es distinto, excepto posiblemente
por el primero y el ltimo vrtice. Un camino simple cumple la siguiente restriccin:
vi vj i {0k}, j {1k-1}, ij
Para todo vrtice x, existe el camino simple <x>, que sera el camino de longitud 0.
Un bucle es un camino de longitud 1 que comienza y termina en el mismo vrtice:
<xi, xi>.
Un ciclo es un camino simple <v0, vk> que cumple las siguientes restricciones:
v0= vk
Si es no dirigido, k = 1 (es un bucle) o k 3.
La Figura 5.4 ilustra estos conceptos para un grafo no dirigido y un grafo dirigido.

178 GRAFOS

ESTRUCTURAS DE DATOS

GRAFO NO DIRIGIDO:
a

GRAFO DIRIGIDO:
a

b
e

e
c

<a,b,e,d,c>: camino simple de longitud 4.


<a,c,d,a,b,e>: camino de longitud 5.
<a,e>: no es un camino.
<e,e>: camino, bucle y ciclo

<a,b>: camino simple de longitud 1.


<e,d,a,b>: camino de longitud 3.
<a,c,d>: no es un camino.
<e,e>: camino, bucle y ciclo

Figura 5.4. Caminos, bucles y ciclos en un grafo dirigido y no dirigido.


5.2.5.

Grafos conexos

Sea G = (V, A) un grafo no dirigido, se le denomina conexo si existe un camino


entre dos vrtices cualesquiera de G. Para un grafo dirigido G, su grafo asociado no
dirigido es aquel que se obtiene ignorando la direccin de las aristas. G se considera conexo
si su grafo asociado es conexo. La Figura 5.5 muestra ejemplos de grafos conexos y no
conexos.
GRAFO 1: NO DIRIGIDO CONEXO
a

GRAFO 2: DIRIGIDO CONEXO


a

e
c

e
c

GRAFO 3: NO DIRIGIDO NO CONEXO


a

GRAFO 4: DIRIGIDO NO CONEXO


a

b
e

e
c

Figura 5.5. Ejemplos de grafos conexos y no conexos

ESTRUCTURAS DE DATOS

GRAFOS 179

5.2.6. Grafos valorados y grafos etiquetados2


Un grafo valorado (o ponderado) es una terna <V, A, f> donde <V, A> es un grafo y
f es una funcin cualquiera, denominada funcin de coste, que asocia un valor o peso a
cada arista en el grafo. El peso de un camino en un grafo con pesos es la suma de los pesos
de todas las aristas atravesadas. En un grafo etiquetado, la funcin f tiene como imagen un
conjunto de etiquetas no numricas.
GRAFO VALORADO

GRAFO ETIQUETADO
Museo Municipal

a
3.6
c

1
0.5
2

est-en

estilo
b
2
2
d

diseado-por

2.8
nacido-en

e
5

churrigueresco
estilo
Plaza Mayor

Figura 5.6. Grafo valorado y grafo etiquetado

Pedro de Ribera

En este curso no se van a tratar los grafos valorados ni etiquetados.

Madrid
est-en

Salamanca

180 GRAFOS

ESTRUCTURAS DE DATOS

5.3. IMPLEMENTACIONES DE GRAFOS


Los dos tipos de implementacin ms frecuentes (independientemente del lenguaje de
programacin) para la representacin de grafos son las matrices de adyacencias y las
listas de adyacencias. En este tema, se detallarn ambas implementaciones en el lenguaje
Java.
5.3.1. Interfaz del TAD Grafo.
Los mtodos de objeto que podremos utilizar sobre las variables de la clase Grafo (no
etiquetado ni ponderado) aparecen en la siguiente interfaz:
import java.io.IOException;
public interface Grafo {
public void insertaVertice(int n);
/** Inserta un vrtice en el grafo siempre que no se supere el nmero mximo
de
nodos permitidos **/
public void eliminarVertice(int v)
/** Elimina un vrtice del grafo **/
public void insertaArista(int i, int j);
/** Inserta una arista entre los vrtices i y j **/
public void eliminarArista(int i, int j);
/** Elimina la arista entre los vrtices i y j **/
public boolean esVacio(Grafo g);
/** Devuelve true si el grafo no contiene ningn vrtice **/
public boolean existeArista(int i, int j);
/** Devuelve true si existe una arista que una los vrtices i y j. **/
public int gradoIn(int i)
;
/** Devuelve el grado de entrada del vrtice i **/
public int gradoOut(int i);
/** Devuelve el grado de salida del vrtice i **/
public int incidencia (int i)
/** Devuelve la incidencia del vrtice i **/
public int tamano();
/** Devuelve el tamao (nmero de aristas) del grafo **/
public boolean esDirigido (Grafo g) ;
/** Devuelve true si el grafo g es dirigido **/
public void ponerMaxNodos(int n);
/** Asigna el nmero mximo de nodos o vrtices permitidos en el grafo**/
public void ponerDirigido(boolean d);
/** Determina si es un grafo dirigido o no dirigido **/
}

ESTRUCTURAS DE DATOS

5.3.2.

GRAFOS 181

Matriz de adyacencias

Una matriz de adyacencias A (implementada como una matriz bidimensional)


determina las adyacencias entre pares de vrtices de un grafo. En una matriz de
adyacencias, los vrtices se conciben como enteros en el conjunto {0,1,,n-1} y las aristas
como pares de tales enteros. Esto permite almacenar referencias a las aristas en las celdas
de la matriz bidimensional de nxn. Cada fila y cada columna representan un vrtice del
grafo y cada posicin representa una arista (o la ausencia de esta) cuyo vrtice origen se
encuentra en la fila y vrtice final se encuentra en la columna. La Figura 5.7 muestra la
representacin grfica de un grafo y su matriz de adyacencias.

b
e
c

a
b
c
d
e

a
0
0
1
1
0

b
1
0
0
1
1

c
0
0
0
0
0

d
0
0
1
0
1

e
0
0
0
0
1

Figura 5.7. Grafo y matriz de adyacencias correspondiente


Ntese que la matriz de adyacencias para un grafo no dirigido es una matriz simtrica
como se puede apreciar en la Figura 5.8:

b
e
c

a
b
c
d
e

a
0
1
1
1
0

b
1
0
0
0
1

c
1
0
0
1
0

d
1
0
1
0
0

e
0
1
0
0
0

Figura 5.8. Grafo y matriz de adyacencias para un grafo no dirigido


En una tabla de adyacencias, los vrtices se representan mediante ndices. As, sea un
grafo con los vrtices {a,b,c,d,e}, estos sern representados mediante sus ndices
{0,1,2,3,4} tal y como se muestra en la Figura 5.9.

b
e

VRTICES: a

NDICES:

Figura 5.9. Relacin entre vrtices e ndices

182 GRAFOS

ESTRUCTURAS DE DATOS

De esta manera, podemos definir una matriz A bidimensional de n x n donde la celda


[i, j] guarda informacin referente a la arista (v, w), en caso de que exista, donde v es el
vrtice con ndice i y w es el vrtice con ndice j. Existen varias posibilidades para
representar la arista (v, w) en su correspondiente celda A [i, j]:
Si no es etiquetado: un valor booleano que toma el valor 1 si existe la arista y 0 en
caso contrario.
Si es valorado: un real con el valor f (i, j) si existe la arista. En caso contrario,
toma el valor . Se obtendra la matriz de costes del grafo.
Si es etiquetado: una etiqueta con el valor f (i, j) si existe la arista. En caso
contrario, toma el valor etiqueta imposible. Se obtendra la matriz de etiquetas
del grafo.
A continuacin, se muestra una posible implementacin de un grafo en una matriz de
adyacencias en Java. Se asume que se trata de un grafo simple no etiquetado (tanto dirigido
como no dirigido).
5.3.2.1. Representacin de la clase Grafo
La siguiente sintaxis muestra la implementacin de la clase grafo con un matriz de
adyacencias:
public class GrafoMA implements Grafo {
private boolean dirigido;
// Indica si es dirigido o no.
private int maxNodos;
// Tamao mximo de la tabla.
private int numVertices;
// Nmero de vrtices del grafo.
private boolean matrizAdy [ ] [ ];
// Matriz de adyacencias del grafo.
}

La representacin grfica de una variable de la clase Grafo llamada grafo1 que


represente el grafo de la Figura 5.7 sera:

grafo1

true

Variable
esttica

dirigido

maxNodos

numVertices

Clase Grafo

matrizAdy

a
b
c
d
e

a
0
0
1
1
0

b
1
0
0
1
1

c
0
0
0
0
0

Figura 5.10. Representacin grfica de la variable grafo1

d
0
0
1
0
1

e
0
0
0
0
1

ESTRUCTURAS DE DATOS

GRAFOS 183

5.3.2.2. Constructores de la clase Grafo3.


Se utiliza para crear un grafo vaco, con un tamao mximo y nmero de vrtices
igual a 0. Se pasa como argumento un booleano que indica si se trata de un grafo dirigido o
no dirigido.
public GrafoMA (boolean d) {
maxNodos = numVertices = 0;
dirigido = d;
}

Tambin se puede incluir un constructor al que se le pasen como argumentos el


nmero de vrtices del grafo y el valor para el campo dirigido:
public GrafoMA (int n, boolean d) {
dirigido = d;
maxNodos = n;
numVertices = 0;
matrizAdy = new boolean[n][n];
}

5.3.2.3. Algoritmos bsicos de modificacin: insertar y eliminar aristas.


La insercin de una arista (i, j) en la matriz supone asignar a la celda correspondiente
el valor true. Si se trata de un grafo dirigido, debe tenerse en cuenta que el vrtice origen i
corresponde a fila, mientras que el vrtice destino j corresponde la columna. En caso de que
se trate de un grafo no dirigido, debe recordarse que la arista (i, j) es igual a la arista (j, i)
para que la matriz mantenga la propiedad de la simetra.
public void insertaArista (int i, int j) {
matrizAdy [i] [j] = true;
if (!dirigido)
matrizAdy [j] [i] = matrizAdy [i] [j];
}

La eliminacin de la arista (i, j) es sencilla, consiste en asignar el valor false a la


celda correspondiente de la matriz. En caso de que se trate de un grafo no dirigido, habr de
modificarse igualmente el valor correspondiente a la arista (j, i):
public void eliminarArista (int i, int j) {
matrizAdy [i] [j] = false;
if (!dirigido)
matrizAdy [j] [i] = false;
}

Antes de ejecutar esta operacin deber asegurarse de que no existe previamente ningn grafo con el mismo
nombre pues su ejecucin hara que se perdiese el contenido del grafo preexistente.

184 GRAFOS

ESTRUCTURAS DE DATOS

5.3.2.4. Algoritmos bsicos de modificacin: insertar vrtices


La modificacin de los vrtices del grafo supone un grado de complejidad mayor que
el tratamiento de las aristas. Para la insercin de un vrtice, el cdigo aqu mostrado
simplifica el mtodo de manera que no deja insertar vrtices si se supera el lmite de
vrtices del grafo (valor del campo maxNodos). En caso de que no se supere el nmero
mximo de vrtices, simplemente se asigna el valor false a las celdas correspondientes y
se actualiza el campo numVertices:
public void insertaVertice (int n) {
if ( n > maxNodos - numVertices )
System.out.println ("Error, se supera el nmero de nodos mximo");
else {
for (int i=0; i < numVertices + n; i++) {
for (int j = numVertices; j < numVertices + n; j++)
matrizAdy [i] [j] = matrizAdy [j] [i] = false;
}
numVertices = numVertices + n;
}
}

La eliminacin de un vrtice supondra cambiar los ndices de los vrtices. De manera


que no se va a reproducir esta operacin. Se deja al alumno como ejercicio la posible
implementacin de eliminacin de un vrtice.
5.3.2.5. Otros mtodos
Puede ser interesante conocer el grado de entrada y de salida de un vrtice. Retmese
el grafo y su matriz de la Figura 5.7. En un grafo dirigido, el grado de entrada de un vrtice,
por ejemplo d, viene dado por la suma de los valores de la columna d; mientras que su
grado de salida vendra dado por la suma de los valores de la fila d:

b
e
c

a
b
c
d
e

a
0
0
1
1
0

b
1
0
0
1
1

c
0
0
0
0
0

d
0
0
1
0
1

e
0
0
0
0
1

Grado de salida (b) = 0

Grado de entrada (b) = 3

Figura 5.11. Grados de entrada y salida en una matriz de adyacencias.


public int gradoIn(int j) {
int gIn = 0;
for (int i = 0; i < numVertices; i++)
//recorrido por filas
if (matrizAdy [i] [j])
gIn++;
//manteniendo la posicin de la columna en [j]
return gIn;
}

ESTRUCTURAS DE DATOS

GRAFOS 185

public int gradoOut(int i) {


int gOut = 0;
for (int j= 0; j < numVertices; j++)
if (matrizAdy [i][j])
gOut++;
// manteniendo la posicin de la fila en [i]
return gOut;
}

En un grafo no dirigido, la incidencia de un vrtice vendra dada por su grado de


entrada:
public int incidencia (int i) {
if (!dirigido)
return gradoIn (i);
else return gradoIn (i) + gradoOut (i);
}

El tamao de un grafo viene dado por el nmero de aristas. Debe notarse que las
aristas de un grafo no dirigido se cuentan dos veces.
public int tamano() {
int tm = 0;
for (int i = 0; i < numVertices; i++)
for (int j=0; j < numVertices; j++)
if (matrizAdy [i] [j])
tm++;
if (dirigido)
return tm;
else return tm/2;
}

Para comprobar si un grafo es dirigido o no, basta con comprobar si se trata de una
matriz simtrica, donde la posicin [i, j] = [j, i]:
public boolean esDirigido (Grafo g) {
boolean dir = true;
for (int i = 0; i < numVertices; i++)
for (int j = 0; j < numVertices; j++)
if (matrizAdy [i] [j] != matrizAdy [j] [i])
dir = false;
return dir;
}

Por ltimo, el siguiente mtodo imprime una matriz de adyacencias de un grafo:


public void imprimirTabla () {
System.out.println ("La matriz contiene " + numVertices + " vrtices: \n");
for (int i = 0; i < numVertices; i++) {
for (int j = 0; j < numVertices; j++) {
if (matrizAdy [i] [j])
System.out.print ("1 ");
else System.out.print ("0 ");
}
}
}

186 GRAFOS

5.3.3.

ESTRUCTURAS DE DATOS

Lista de adyacencias

Otra implementacin frecuente para la estructura grafo es la lista de adyacencias. En


una lista de adyacencias, a cada vrtice i se le asocia una lista enlazada con todos los
vrtices j adyacentes a i. De esta forma slo se reserva memoria para las aristas adyacentes
a i y no para todas las posibles aristas que pudieran tener como origen i (como ocurre en
una matriz de adyacencias).
El grafo, por tanto, se representa por medio de un vector de n componentes, siendo n
el nmero de vrtices del grafo, donde cada componente constituye la lista de adyacencias
correspondiente a cada uno de los vrtices del grafo. Cada nodo de la lista consta de un
campo indicando el vrtice adyacente. En caso de que el grafo sea etiquetado o valorado,
habr que aadir un segundo campo para mostrar el valor de la etiqueta o el peso de la
arista.
La Figura 5.12 muestra un grafo y su representacin en una lista de adyacencias.

1
4
2

null

null

null

null

null

Figura 5.12. Grafo dirigido y lista de adyacencias asociada

5.3.3.1. Representacin de la clase Grafo como lista de adyacencias


Para la implementacin de la clase Grafo como una lista de adyacencias, se har uso
de la clase Lista Calificada Ordenada (tal y como se vio en el tema 3.6.), as como la clase
NodoLista con las siguientes variables miembro y constructores:
public class NodoLista {
public int clave;
public NodoLista sig;
public NodoLista (int dato, NodoLista siguiente) {
clave = dato;
sig = siguiente;
}
}

public class Lista {


public NodoLista inicio;
public Lista () {
inicio = null;
}

ESTRUCTURAS DE DATOS

GRAFOS 187

La implementacin de los distintos mtodos de la clase Grafo aqu propuesta utiliza


los siguientes mtodos de Lista:
void insertar (int x) ;
void eliminar (int x);
boolean busqueda (int x);

La siguiente sintaxis muestra la implementacin de la clase grafo con una lista de


adyacencias:
public class GrafoLA implements Grafo {
boolean dirigido;
// Indica si es dirigido o no.
int
maxNodos;
// Tamao mximo de la tabla.
int
numVertices;
// Nmero de vrtices del grafo.
Lista
listaAdy [];
// Vector de listas de adyacencias del grafo.
}

Figura 5.13. Clase Grafo


5.3.3.2. Constructores de Grafo
El constructor Grafo(boolean d) se utiliza para crear un grafo vaco, con un tamao
mximo y nmero de vrtices igual a 0. El argumento booleano indica si se trata de un
grafo dirigido o no dirigido.
public GrafoLA (boolean d) {
maxNodos = numVertices = 0;
dirigido = d;
}

Tambin se puede incluir un constructor al que se le pasen como argumentos el


nmero de vrtices del grafo y el valor para el campo dirigido. Se crea un vector de n listas
inicializadas.
public GrafoLA (int n, boolean d) {
dirigido = d;
maxNodos = n;
numVertices = 0;
listaAdy = new Lista[n];
}

188 GRAFOS

ESTRUCTURAS DE DATOS

5.3.3.3. Algoritmos bsicos de modificacin: insertar aristas.


La insercin de una arista (i, j) en la lista de adyacencias supone insertar un nodo con
clave j en la lista con ndice i. En caso de que se trate de un grafo no dirigido, debe
recordarse que la arista (i, j) es igual a la arista (j, i) de forma que habr que insertar en la
lista con ndice j el nodo con clave i.
public void insertaArista (int i, int j) {
if (i >= numVertices)
System.out.println ("Error, no existe el vrtice en el grafo");
else {
listaAdy[i].insertar (j);
if (!dirigido)
listaAdy[j].insertar (i);
}
}

La eliminacin de la arista (i, j) consiste en eliminar el nodo con clave j de la lista con
ndice i. Si el grafo es no dirigido, habr que eliminar el nodo con clave i de la lista con
ndice j:
public void eliminaArista (int i, int j) {
if (j >= numVertices)
System.out.println("Error, no existe el vrtice en el grafo");
else {
listaAdy[i].eliminar (j);
if (!dirigido)
listaAdy[j].eliminar (i);
}
}

5.3.3.4. Algoritmos bsicos de modificacin: insertar vrtices


Al igual que en la matriz de adyacencias, no se permite insertar vrtices si se supera
el lmite de vrtices del grafo (valor del campo maxNodos). El mtodo aqu propuesto tiene
como argumento un entero que indica el nmero de vrtices que se desea aadir al grafo.
En caso de que no se supere el nmero mximo de nodos del grafo, se inicializan las n
listas correspondientes a los vrtices que se aaden al grafo.
public void insertaVertice (int n) {
if (n > maxNodos - numVertices)
System.out.println ("Error, se supera el nmero de nodos mximo del grafo");
else
for (int i = numVertices; i < numVertices + n; i++)
listaAdy[i]= new Lista();
numVertices += n;
}

Dado que la eliminacin de un vrtice supondra cambiar los ndices de los vrtices,
no se va a reproducir esta operacin.

ESTRUCTURAS DE DATOS

GRAFOS 189

5.3.3.5. Otros mtodos


Para conocer el grado de entrada de un vrtice v en un grado dirigido, se deben
contar las veces que aparece el nodo con clave v en las distintas listas de adyacencia. Para
ello, se utiliza el mtodo de objeto busqueda de la clase Lista.
public int gradoIn (int v) {
int gIn = 0;
for (int i = 0; i < numVertices; i++)
if (i != v)
if (listaAdy[i].busqueda(v))
gIn++;
return gIn;
}

El grado de salida de un vrtice v viene dado por el nmero de elementos de su lista


de adyacencia (lista con ndice v):
public int gradoOut (int i) { //contar los elementos de la lista
int gOut = 0;
NodoLista aux = listaAdy[i].inicio;
while (aux != null){
gOut++;
aux = aux.sig;
}
return gOut;
}

En un grafo no dirigido, la incidencia de un vrtice vendra dada por su grado de


entrada. En cambio, si se trata de un grafo dirigido, la incidencia de un vrtice i es la suma
de su grado de entrada y salida:
public int incidencia (int i) {
if (!dirigido)
return gradoIn (i);
else return gradoIn (i) + gradoOut (i);
}

El nmero de aristas define el tamao de un grafo. En una implementacin basada en


listas de adyacencias, el tamao del grafo viene dado por el nmero de nodos total de las
listas de adyacencias. Para ello, se hace uso del mtodo auxiliar numElems que recibe
como argumento una lista y devuelve el nmero de nodos de la lista de entrada. En el caso
de que el grafo sea no dirigido, habra que dividir por dos la suma del nmero de nodos de
las listas de adyacencias puesto que las aristas de un grafo no dirigido se cuentan dos veces:

190 GRAFOS

ESTRUCTURAS DE DATOS

public int tamanno () {


int tm = 0;
for (int i=0; i<numVertices; i++)
tm += numElementos (listaAdy[i]);
if (!dirigido)
tm = tm / 2;
return tm;
}
static int numElementos (Lista lista) {
NodoLista aux = lista.obtenerInicio();
int resul = 0;
while (aux != null) {
resul += 1;
aux = aux.obtenerSig();
}
return resul;
}

El siguiente mtodo booleano devuelve un valor verdadero en caso de que el grafo


sea no dirigido. En un grafo dirigido simple el par (i, j) es necesariamente distinto del par
(j, i), mientras que en un grafo no dirigido se cumple que el par (i, j) = (j, i). El mtodo
propuesto comprueba para, cada par de vrtices i, j, que el vrtice j se encuentre en la lista
de adyacencias del vrtice i; e igualmente que el vrtice i se encuentre en la lista de
adyacencias del vrtice j.
public boolean esNoDirigido () {
boolean dir = true;
for (int i=0; i<numVertices; i++)
for (int j=0; j<numVertices; j++)
if (listaAdy[i].busqueda (j) != listaAdy[j].busqueda (i))
dir = false;
return dir;
}

Por ltimo, el siguiente mtodo imprime la lista de adyacencias para un grafo, dicho
mtodo utiliza el mtodo de clase escribir (Lista):
public void imprimirGrafo () {
System.out.println("Tamao mximo del grafo: " + maxNodos + "\n");
System.out.println("El grafo contiene " + numVertices + " vrtices: \n");
for (int i = 0; i < numVertices; i++) {
System.out.print ("vrtice " + i + ": ");
escribir (listaAdy [i]);
}
}
static void escribir (Lista lista) {
NodoLista aux;
aux = lista.inicio;
while (aux != null) {
System.out.print (aux.clave +", ");
aux = aux.sig;
}
System.out.println ("FIN");
}

ESTRUCTURAS DE DATOS

GRAFOS 191

5.3.4. Consideraciones sobre la implementacin del grafo como matriz o como lista
de adyacencias
La principal ventaja de la matriz de adyacencias es el rpido acceso a la informacin
de aristas. Es una estructura recomendable si se van a realizar consultas frecuentes del
estilo: existe la arista (u, v) en el grafo? O, en caso de ser un grafo valorado, cul es el
coste de la arista (u, v)? Tambin es una estructura recomendable para grafos con pocos
vrtices o bien grafos con muchos vrtices y muchas aristas.
La principal desventaja que presenta la matriz es que requiere un almacenamiento
proporcional al cuadrado del nmero de vrtices del grafo, aunque el grafo contenga
muchas menos aristas de los posibles.
Respecto a las listas de adyacencias, estas ahorran memoria en la representacin de
grafos con muchos vrtices y pocas aristas. Asimismo, si el procesamiento se centra en el
tratamiento de los vrtices adyacentes a uno dado, la lista de adyacencias es preferible a la
matriz.

192 GRAFOS

ESTRUCTURAS DE DATOS

5.4. RECORRIDOS DE GRAFOS


El concepto de recorrido del grafo, como se ha visto en el tema 4 de rboles, consiste
en visitar todos los vrtices del grafo sucesivamente de manera sistemtica de manera que
cada vrtice se visite una nica vez.
En el recorrido de un grafo, existen dos tipos de vrtices:
Vrtices visitados: vrtices ya visitados en el recorridos
Vrtices frontera: vrtices que aun no ha sido visitados pero estn conectados con
algn vrtice visitado (estn pendientes de visitar).
Al igual que un rbol (de hecho, un rbol es un tipo de grafo orientado sin ciclos), un
grafo puede ser recorrido en profundidad o en amplitud. Existen dos diferencias
fundamentales a la hora de recorrer un grafo respecto de un rbol:
a)

b)

5.4.1.

Puesto que un rbol es un grafo orientado sin circuitos, al avanzar en el recorrido


no cabe la posibilidad de que se vuelva a visitar un vrtice ya visitado. En el
recorrido de un grafo s cabe la posibilidad de al avanzar visitar un vrtice ya
visitado. Se deber implementar algn mecanismo que evite esta situacin.
Partiendo de la raz de un rbol se pueden visitar todos los vrtices, mientras que
en un grafo se puede dar la posibilidad de que no se alcancen todos los vrtices
desde un vrtice. Habra que comenzar el recorrido en otro vrtice para poder
alcanzar todos los vrtices.

Recorrido en profundidad

En el recorrido en profundidad, se da preferencia a visitar a los vrtices conectados


con el ltimo visitado. Dado un grafo G, en el que inicialmente ningn vrtice ha sido
visitado, el recorrido en profundidad selecciona un vrtice v de G como vrtice inicial, que
se marca como visitado. Entonces, se busca un vrtice no visitado adyacente a v, w, que se
marca como visitado y se selecciona como vrtice inicial para reiniciar el recorrido. Una
vez que se han visitado todos los vrtices alcanzables de w se vuelve a v y se selecciona un
nuevo vrtice no visitado. Cuando se han visitado todos los vrtices alcanzables desde v, se
da por terminado el recorrido de v. Si quedan vrtices por visitar, se selecciona uno de ellos
como nuevo vrtice de partida y se reproduce todo el proceso. Es decir:
Sea un grafo G y un vrtice v que pertenece a G,
marcar v como visitado
mientras queden vrtices sin visitar hacer
seleccionar un vrtice no visitado, v,
avanzarEnProfundidad a partir de v
fin mientras

Donde avanzarEnProfundidad consiste en:


marcar el vrtice v como visitado
seleccionar un vrtice adyacente a v no visitado, w
avanzar en profundidad a partir de w.

ESTRUCTURAS DE DATOS

GRAFOS 193

Por ejemplo, sea el siguiente grafo G:


2
5
1

Figura 5.14.

Ejemplo de grafo

Se toma como vrtice inicial 1,


v1 visitado
(el conjunto de vrtices adyacentes a v1 es 2,3)
Se toma vrtice 2:
v2 visitado
(el conjunto de vrtices adyacentes a v2 es 3,4)
Se toma vrtice 3
v3 visitado (el conjunto de vrtices adyacentes a v3 es 1)
//v1 ya est visitado por lo que se termina el recorrido en profundidad a partir del vrtice 3.

Se toma el vrtice 4
v4 visitado
(el conjunto de vrtices adyacentes a v4 es 3)
//v3 ya est visitado por lo que se termina el recorrido en profundidad a partir del
vrtice 4.
//v3 ya est visitado por lo que termina el recorrido en profundidad a partir del vrtice 3.

No es posible alcanzar ms vrtices desde v1, de manera que hay que seleccionar un nuevo
vrtice desde el que recomenzar la exploracin en profundidad, se elige el vrtice 5:
v5 visitado (el conjunto de vrtices adyacentes a v5 es 4)
//v4 ya est visitado de manera que se termina el recorrido en profundidad a partir del vrtice 5

--- FIN---

El algoritmo de recorrido en profundidad cuenta con una parte recursiva que recorre
parcialmente un subgrafo a partir de un vrtice de inicio y una parte no recursiva que se
encarga de relanzar el proceso en cada vrtice no visitado. Se utiliza adems un vector de
valores lgicos para marcar los vrtices visitados. Se utilizan los ndices de los vrtices para
iniciar y marcar el proceso de recorrido.
El cdigo en Java para un recorrido en profundidad es el siguiente:
//procedimiento recursivo
public void recorrerProfundidad (Grafo g, int v, boolean [ ] visitados) {
//se marca el vrtice v como visitado
visitados [v] = true;
//el tratamiento del vrtice consiste nicamente en imprimirlo en pantalla
System.out.println (v);
//se examinan los vrtices adyacentes a v para continuar el recorrido
for (int i = 0; i < g.numVertices; i++) {
if ((v != i) && (!visitados [i]) && (g.existeArista (v, i)) )
recorrerProfundidad (g, i, visitados);
}
}
//procedimiento no recursivo
public void profundidad (Grafo g) {
boolean visitados [ ] = new boolean [g.numVertices];

194 GRAFOS

ESTRUCTURAS DE DATOS

for (int i = 0; i < g.numVertices; i++) //inicializar vector con campos false
visitados [i] = false;
for (int i = 0; i < g.numVertices; i++) { //Relanza el recorrido en cada
if (!visitados [i])
//vrtice visitado
recorrerProfundidad (g, i, visitados);
}
}

5.4.2.

En amplitud

En un recorrido en amplitud, se elige un vrtice no visitado v, como punto de partida


y se pasa a visitar cada uno de sus vrtices adyacentes, para continuar posteriormente
visitando los adyacentes a estos ltimos y as sucesivamente hasta que no se puedan
alcanzar ms vrtices. Si queda algn vrtice sin visitar, se selecciona y se vuelve a relanzar
el proceso.
Para realizar un recorrido en amplitud de un grafo es necesario utilizar una estructura
de datos cola (cf. el recorrido en amplitud de la clase rbol). En la cola se van almacenando
los vrtices a medida que se llega a ellos. Los vrtices se marcan en la cola como visitados
y son tratados cuando se extraen de la cola al tiempo que se introducen en la cola los
adyacentes al vrtice tratado.
El cdigo en Java para el recorrido en amplitud es el siguiente:
public static void amplitud (Grafo g) {
Cola cola = new Cola ();
boolean visitados [ ] = new boolean [g.obtenerNumVertices()];
int v; //vrtice actual
//Se inicializa el vector visitados [] a false
for (int i = 0; i < g.obtenerNumVertices (); i++)
visitados [i] = false;
//El recorrido en amplitud se inicia en cada vrtice no visitado
for (int i = 0; i < g.obtenerNumVertices (); i++) {
//se pone en la cola el vrtide de partida y se marca como visitado
if (!visitados [i]){
cola.encolar (i);
visitados [i] = true;
while (!cola.estaVacia ()) {
v = cola.desencolar ();
//desencolar y tratar el vrtice
System.out.println (v);
//y encolo los nodos adyacentes a v.
for (int j = 0; j < g.obtenerNumVertices (); j++){
if ((v !=j) && (g.existeArista (v, j) && (!visitados [j])) {
cola.encolar ( j );
visitados [j] = true;
}
}
}
}
}
}

ESTRUCTURAS DE DATOS

GRAFOS 195

TEMA 5 .................................................................................................................... 173


5.1.

definicin de grafo...................................................................................... 173

5.2.

terminologa y conceptos............................................................................ 175

5.2.1.

Grafos dirigidos y no dirigidos ........................................................... 175

5.2.2.

Incidencia, adyacencia y grado de un vrtice ..................................... 175

5.2.3.

Grafos simples y multigrafos ............................................................. 176

5.2.4.

Camino, bucle y ciclo ......................................................................... 177

5.2.5.

Grafos conexos ................................................................................... 178

5.2.6.

Grafos valorados y grafos etiquetados ............................................... 179

5.3.

Implementaciones de Grafos ...................................................................... 180

5.3.1.

Interfaz del TAD Grafo. ..................................................................... 180

5.3.2.

Matriz de adyacencias ........................................................................ 181

5.3.3.

Lista de adyacencias ........................................................................... 186

5.3.4. Consideraciones sobre la implementacin del grafo como matriz o


como lista de adyacencias .......................................................................................... 191
5.4.

Recorridos de grafos ................................................................................... 192

5.4.1.

Recorrido en profundidad ................................................................... 192

5.4.2.

En amplitud ........................................................................................ 194

Vous aimerez peut-être aussi