Sunteți pe pagina 1din 125

Introduccion

En este documento, se intentara explicar algunos temas de nuestro temario.


Para los que cursamos la carrera de Ingeniera en Sistemas Computacionales, estos
temas son necesarios y bsicos para la carrera, adems que son una introduccin a
la base de datos.
En este documento, esta contenido por explicaciones, imgenes, pseudocdigo
adems de el mismo cdigo necesario para llevarlo a cabo en distintos tipos de
aplicaciones.

Indice:
Unidad 1 Estructura de Datos
1. Introduccion a las estructuras de datos
1.1 Clasificacion de las estructuras de datos
1.2 Tipos de datos abstractos
1.3 Ejemplos de datos abstractos
1.4 Manejo de memoria
1.5 Analisis de algoritmos
Unidad 2 Recursividad
2.1 Definicion de mtodos recursivos
2.2 Metodos recursivos
2.3 Ejemplos de casos recursivos
Unidad 3 Estructuras lineales
3.1 Pilas
3.1.1 Representacion
3.2.2 Operaciones basicas
3.1.3 Aplicaciones Pilas
3.2 Colas
3.2.1 Representacion
3.2.2 Operaciones basicas
3.2.3 Tipos de colas

3.2.3.1 Simples
3.2.3.2 Colas Dobles
3.2.3.3 Circulares
3.2.4 Aplicaciones Colas de prioridad
3.3 Listas
3.3.1 Operaciones basicas con listas
3.3.2 Tipos de listas
3.3.3 Listas simplemente enlazadas
3.3.4 Listas doblemente enlazadas
3.3.5 Listas circulares
3.3.6 Aplicaciones Listas

Unidad I Estructura de Datos


Las estructuras de datos determinan la conexin lgica entre los datos y afectan el
procesamiento fsico de los datos.
Una estructura de datos es una clase de datos que se puede caracterizar por su organizacin y
operaciones definidas sobre ella. Algunas veces a estas estructuras se les llama tipos de datos.
Una estructura de datos es una coleccin de datos que pueden ser caracterizados por su
organizacin y las operaciones que se definen en ella

1 Introduccin a las estructuras de datos.


En un lenguaje de programacin, un tipo de dato esta definido por el conjunto de
valores que representa y por el conjunto de operaciones que se pueden realizar con
dicho tipo de dato. Por ejemplo, el tipo de dato entero en Java puede representar
nmeros en el rango de -2^31 a 2^31-1 y cuenta con operaciones como suma, resta,
multiplicacin, divisin, etc.
Por otro lado, podemos decir que en la solucin de un problema a ser procesado por
un computador podemos encontrar dos grandes tipos de datos: datos simples y
datos estructurados. Los datos simples son aquellos que, al ser representados por
el computador, ocupan solo una casilla de memoria. Debido a esto, una variable de
un tipo de dato simple hace referencia a un nico valor a la vez. Ejemplo de estos
tipos de datos son los enteros, reales, caracteres y booleanos.
As mismo, los datos estructurados se caracterizan porque su definicin est
compuesta de otros tipos de datos simples, as como de otros datos estructurados.
En este caso, un nombre (identificador de la variable estructurada) hace referencia
no solo a una casilla de memoria, sino a un grupo de casillas.

En programacin, el trmino estructura de datos se utiliza para referirse a una


forma de organizar un conjunto de datos que se relacionan entre si, sean estos
simples o estructurados, con el objetivo de facilitar su manipulacin y de operarlo
como un todo.
Por otro lado, tenemos el trmino Tipo de Dato Abstracto, o TDA, que es muy
comnmente utilizado como equivalente al trmino estructura de datos para referirse
justamente a un tipo de dato estructurado que representa un concepto a travs de la
definicin de sus caractersticas(datos que lo conforman) y de sus
operaciones(algoritmos que manipulan los datos que lo conforman)

Operaciones
Sobre una estructura de datos se puede efectuar diferentes tipos de operaciones,
entre las ms importantes estn:

Insercin. Es aquella mediante la cual se incluye un nuevo elemento en la estructura.

Modificacin. Permite variar parcial o totalmente el contenido de la informacin de los


elementos de la estructura.

Eliminacin. Como su nombre lo indica, es la que permite suprimir elementos de la


estructura.

Navegar por la estructura: Esta es una operacin bsica que garantiza que se puede
recuperar informacin almacenada.

Bsqueda. Permite determinar si un elemento se encuentra o no en la estructura.

Consulta de la informacin. Permite obtener informacin de uno o ms elementos de


la estructura.

Copia parcial o total: Mediante esta operacin se puede obtener total o parcialmente

una estructura con caractersticas similares a la original.

Prueba. Permite determinar si uno o varios elementos cumplen determinadas


condiciones.

Verificar si es vaca . Permite determinar si existen o no elementos sobre la


estructura.

CLASIFICACION
Una clasificacin de estructuras de datos es segn dnde residan: Internas y externas.
Si una estructura de datos reside en la memoria central del computador se
denomina estructura de datos interna. Recprocamente, si reside en un soporte externo,
se denomina estructura de datos externa.
Las estructuras de datos internas pueden ser de dos tipos:

Estructuras de Datos Estticas.

Estructuras de Datos Dinmicas.

Estructuras de Datos Estticas


Tienen un nmero fijo de elementos que queda determinado desde la declaracin de la
estructura en el comienzo del programa. Ejemplo los arreglos. Las estructuras de datos
estticas, presentan dos inconvenientes:
1. La reorganizacin de sus elementos, si sta implica mucho movimiento puede ser muy costosa.
Ejemplo: insertar un dato en un arreglo ordenado.
2. Son estructuras de datos estticas, es decir, el tamao ocupado en memoria es fijo, el arreglo
3.
4.
5. podra llenarse y si se crea un arreglo de tamao grande se estara desperdiciando memoria.

Estructuras de Datos Dinmicas


Las estructuras de datos dinmicas nos permiten lograr un importante objetivo de la
programacin orientada a objetos: la reutilizacin de objetos. Al contrario de un arreglo, que
contiene espacio para almacenar un nmero fijo de elementos, una estructura dinmica de
datos se ampla y contrae durante la ejecucin del programa.
A su vez, este tipo de estructuras se pueden dividir en dos grandes grupos segn la forma en la
cual se ordenan sus elementos.

Lineales

No lineales

Estructuras de Datos Lineales


En este tipo de estructuras los elementos se encuentran ubicados secuencialmente. Al ser
dinmica, su composicin vara a lo largo de la ejecucin del programa que lo utiliza a travs
de operaciones de insercin y eliminacin. Dependiendo del tipo de acceso a la secuencia,
haremos la siguiente distincin:

Listas: podemos acceder (insertar y eliminar) por cualquier lado.

Pilas: slo tienen un nico punto de acceso fijo a travs del cual se aaden, se eliminan o se
consultan elementos.

Colas: tienen dos puntos de acceso, uno para aadir y el otro para consultar o eliminar
elementos.

Estructuras de Datos No Lineales


Dentro de las estructuras de datos no lineales tenemos los rboles y grafos. Este tipo de
estructuras los datos no se encuentran ubicados secuencialmente. Permiten resolver
problemas computacionales complejo

1.2 Tipos de Datos Abstractos


Un algoritmo es una secuencia finita de operaciones, organizadas para realizar una tarea
determinada.
Las estructuras de datos son la forma en que se organizan los datos para ser usados.
Puede ser una coleccin de variables, posiblemente de diferentes tipos de datos, conectadas

de un modo determinado.
Una estructura de datos bien organizada debe permitir realizar un conjunto de acciones sobre
los datos de tal forma de minimizar el uso de los recursos y el tiempo empleado para efectuar
la operacin.
Abstraccin
La abstraccin es un mecanismo fundamental para la comprensin de fenmenos o
situaciones que implican gran cantidad de detalles.
Abstraccin es la capacidad de manejar un objeto (tema o idea) como un concepto general, sin
considerar la enorme cantidad de detalles que pueden estar asociados con dicho objeto.
Ejemplo, se puede saber conducir un automvil sin conocer el tipo del modelo o cmo est
fabricado.
La abstraccin se utiliza para suprimir detalles irrelevantes, mientras se enfatiza en los
relevantes o significativos.
El beneficio principal de la abstraccin es que facilita al programador pensar acerca del
problema a resolver. Uno de los principios importantes del diseo de software es el de la
abstraccin y ocultacin de la informacin.
Abstraccin de datos es una tcnica que permite inventar nuevos tipos de datos que sean ms
adecuados a una aplicacin y, por consiguiente, facilitar la escritura del programa
Tipo Abstracto de Dato (TDA)
Qu es un TDA?
Un TDA es un modelo matemtico con una coleccin de operaciones definidas sobre el
modelo (Aho, Hoperoft y Ullman. Fundamental Structures of Computer Science, 1981).
Una clase de objetos definida por una especificacin independiente de la representacin
(Guttag Abstract Data Type and development of data structures ACM . Vol 20-6, 1977)
Es un tipo de dato definido por el usuario a travs de una especificacin y una implementacin
de los objetos abstractos. (Rowe , types ACM sigplan, Vol 16-1, 1980).
Un tipo de dato abstracto (TDA) o Tipo abstracto de datos (TAD) es un modelo
matemtico compuesto por una coleccin de operacionesdefinidas sobre un conjunto
de datos para el modelo. Annimo
Un TDA es un tipo de dato definido por el usuario para representar una entidad (abstraccin) a
travs de sus caractersticas (datos o atributos) y sus operaciones o funciones (algoritmos que
manipulan los datos). Hilda Contreras

Un TDA est caracterizado por un conjunto de operaciones (mtodos) al cual le denominamos


usualmente como su interfaz pblica y representan el comportamiento del TDA; mientras que
la implementacin como la parte privada del TDA est oculta al programa cliente que lo usa.
Todos los lenguajes de alto nivel tienen predefinidos TDA.
Con mucha frecuencia se utilizan los trminos TDA y Abstraccin de Datos de manera
equivalente, y esto es debido a la similitud e interdependencia de ambos. Sin embargo, es
importante definir por separado los dos conceptos.
La abstraccin de datos consiste en ocultar las caractersticas de un objeto y obviarlas, de
manera que solamente utilizamos el nombre del objeto en nuestro programa. Esto es similar a
una situacin de la vida cotidiana. Cuando se dice la palabra perro, usted no necesita que se
le diga lo que hace el perro. Usted ya sabe la forma que tiene un perro y tambin sabe que los
perros ladran. De manera que se abstraen todas las caractersticas de los perros en un solo
trmino, perro. A esto se le llama Abstraccin y es un concepto muy til en la programacin,
ya que un usuario no necesita mencionar todas las caractersticas y funciones de un objeto
cada vez que ste se utiliza, sino que son declaradas por separado en el programa y
simplemente se utiliza el trmino abstracto (perro) para mencionarlo.
En el ejemplo anterior, perro es un Tipo de Dato Abstracto y todo el proceso de definirlo,
implementarlo y mencionarlo es a lo que llamamos Abstraccin de Datos.
Otro ejemplo:
Una calculadora es un ejemplo de un TDA que maneja objetos de cantidades numricas y las
operaciones aritmticas sobre dichas cantidades. Usa el sistema decimal para las cantidades y
realiza operaciones de suma, resta, multiplicacin, etc. Sin embargo, Ud. sabe cmo una
calculadora representa las cantidades internamente? En Binario? Decimal? Palitos?
Piedritas? NO no lo sabe y tampoco le hace falta para usar la calculadora.
Un sistema de numeracin es un ejemplo de un tipo de dato abstracto que representa el
concepto de cantidad.

1.2 Modularidad
La modularidad es la posibilidad de dividir una aplicacin en piezas ms pequeas llamadas
mdulos.
Por qu Modulamos las aplicaciones?

Descomponer el problema en partes ms simples

Facilitar la comprensin del sistema y de cada una de sus partes.

Si se produce un error en un mdulo, ste slo afecta a dicho mdulo

Las correcciones debidas a cambios en la especificacin afectan a un nmero


reducido de mdulos.

El sistema est compuesto de una serie de mdulos independientes comunicados


entre s

Cmo se debe modular una aplicacin?

El mdulo debe dejar bien claro cmo hacer uso de l.

El acceso a los servicios de un mdulo debe ser homogneo.

Un mdulo debe estar listo para su uso pero a su vez debe poder mejorarse.

El lenguaje de programacin utilizado debe soportar el uso de mdulos.

1.3 Uso de TDA


Elaborar un ejercicio que permita realizar operaciones sobre conjuntos a travs de los TDA
BitSet y StringTokenizer.
Las operaciones que permitir realizar son:
a) Unin
b) Interseccin
c) Diferencia

Clase BitSet
Java proporciona otra alternativa para manipular los bits y esta es mediante la clase
BitSet(Conjunto de bits) que crea un tipo especial de arreglo que contiene valores de bits. Este
puede aumentar de tamao segn se necesite. Esto lo hace similar a la clase vector. Los
constructores definidos para esta clase son:
BitSet( )
BitSet(int tamao)

La primera opcin crea un objeto por defecto y la segunda opcin permite especificar su
tamao inicial (Esta es la cantidad de bits que puede contener). Todos los bits se inicializan en
cero.

Los mtodos definidos para la clase BitSet son;


Mtodo

Descripcin

void and(BitSet conjBits)

Hace un AND entre los contenidos del


objeto
BitSet
invocante
y
los
especificados por conjBits. El resultado
se coloca en el objeto invocante.

void andNot(BitSet conjBits)

Por cada bit 1 en conjBits, se borra el


correspondiente bit en el BitSet
invocante.

void clear(int indice)

Pone a cero el bit especificado por


ndice.

Object clone( )

Duplica el objeto BitSet invocante.

bolean equals(Object conjBits)

Devuelve true si el conjunto de bits


invocante es equivalente al pasado en
conjBits. De los contrario, el mtodo
devuelve false.

boolean get(int indice)

Devuelve el estado actual del bit en el


ndice especificado.

int hashCode( )

Devuelve el cdigo de dispersin del


objeto invocante.

int length( )

Devuelve el nmero de bits requeridos


para almacenar los contenidos del
BitSet invocante. Este valor es
determinado por la posicin del ltimo
bit a 1.

void or(BitSet conjBits)

Hace un OR de los contenidos del


objeto BitSet invocante con los del
especificado por conjBits. El resultado
se coloca en el objeto invocante.

void set(int ndice)

Pone en 1 el bit especificado por


ndice.

int size( )

Devuelve el nmero de bits en el


objeto BitSet invocante.

String toString( )

Devuelve la cadena equivalente del


objeto BitSet invocante.

void xor(BitSet conjBits)

Hace un XOR de los contenidos del


objeto BitSet invocante con los del
especificado por conjBits. El resultado
se coloca en el objeto invocante.

1.4 Manejo de Memoria


La administracin de memoria de una computadora es una tarea fundamental debido a que
la cantidad de memoria es limitada.
El sistema operativo es el encargado de administrar la memoria del sistema y compartirla
entre distintos usuarios y/o aplicaciones.
El RTS (Run Time System) de un lenguaje de programacin administra la memoria para
cada programa en ejecucin.
La ejecucin de un programa requiere que diversos elementos se almacenen en la
memoria:
Cdigo del programa (instrucciones)
Datos
Permanentes
Temporales

Direcciones para controlar de flujo de ejecucin del programa


Memoria esttica y dinmica
A la asignacin de memoria para algunos elementos fijos del programa que es controlada
por el compilador se le llama asignacin de memoria esttica.
A la asignacin y posible recuperacin de memoria durante la ejecucin de un programa y
bajo su control, se le llama asignacin de memoria dinmica.

1.4.1 Manejo de memoria esttica


Para implementar alguna estructura de datos, primero es necesario tener muy claro cmo
va a ser el manejo de memoria.
La diferencia entre estructuras estticas y dinmicas esta en el manejo de memoria.
En la memoria esttica durante la ejecucin del programa el tamao de la estructura no
cambia.
La estructura que maneja memoria esttica son los vectores.
Un vector es una coleccin finita, homognea y ordenada de elementos.
Es finita porque todo arreglo tiene un lmite, homognea porque todos los elementos son del
mismo tipo y ordenada porque se puede determinar cul es el ensimo elemento.
Un vector tiene dos partes: Componentes e ndices
Los componentes hacen referencia a los elementos que forman el arreglo y los ndices
permiten referirse a los componentes del arreglo en forma individual.
Los arreglos se clasifican en:

Unidimensionales (vectores o listas)


Bidimensionales (matrices o tablas)
Multidimensionales

Los arreglos tienen localidades de memoria continuas y para determinar el espacio que deben
ocupar, se requiere conocer la posicin inicial del arreglo en la memoria y el tipo de datos
primitivo del que fue declarado, como se aprecia en la siguiente tabla.
Tipo de dato primitivo

Tamao en Bytes de memoria

byte

char

short

int

float

long

double

Para determinar la direccin fsica de un elemento de un arreglo unidimensional en la memoria


se requiere la siguiente frmula:
Direccin de memoria = Direccin inicial en la memoria + Posicin del arreglo o ndice *
Tamao en bytes del tipo de dato primitivo
Ejemplo. Si tenemos un arreglo de 5 elementos enteros y queremos determinar la direccin de
memoria que ocupa cada uno, tomando en cuenta que la direccin inicial del arreglo es 1300,
el resultado sera es siguiente:

arregl
o

10

20

30

40

50

ndice

direcc
in

13
00

13
04

13
08

13
12

13
16

arreglo[0] = 1300 + 0 * 4 = 1300


arreglo[1] = 1300 + 1 * 4 = 1304
arreglo[2] = 1300 + 2 * 4 = 1308
arreglo[3] = 1300 + 3 * 4 = 1312
arreglo[4] = 1300 + 4 * 4 = 1316

Arreglos Bidimensionales.
Un arreglo bidimensional (matriz o tabla), es un conjunto de elementos homogneos definidos
bajo una estructura finita, controlado por dos ndices y su representacin es por un conjunto de
renglones y columnas, en forma de una malla.
Para determinar la direccin fsica de un elemento de un arreglo bidimensional en la memoria
se puede seguir una de las siguientes formulas:
Por renglones.

Direccin de memoria = Direccin inicial en la memoria + (Numero de columnas del arreglo *


Posicin del arreglo en rengln o ndice de rengln + Posicin del arreglo en columna o ndice
de columna) * Tamao en bytes del tipo de dato primitivo

Ejemplo. Si tenemos un arreglo de 3 renglones y 3 columnas con elementos enteros y


queremos determinar la direccin de memoria que ocupa cada uno, tomando en cuenta que la

direccin inicial del arreglo es 2700, el resultado sera es siguiente:

arreglo

direcci
n

40

50

60

270
0

270
4

270
8

70

80

90

271
2

271
6

272
0

70

80

90

272
4

272
8

273
2

arreglo[0][0] = 2700 + (3 * 0 + 0) * 4 = 2700


arreglo[0][1] = 2700 + (3 * 0 + 1) * 4 = 2704
arreglo[0][2] = 2700 + (3 * 0 + 2) * 4 = 2708
arreglo[1][0] = 2700 + (3 * 1 + 0) * 4 = 2712
arreglo[1][1] = 2700 + (3 * 1 + 1) * 4 = 2716
arreglo[1][2] = 2700 + (3 * 1 + 2) * 4 = 2720
arreglo[2][0] = 2700 + (3 * 2 + 0) * 4 = 2724
arreglo[2][1] = 2700 + (3 * 2 + 1) * 4 = 2728
arreglo[2][2] = 2700 + (3 * 2 + 2) * 4 = 2732

Por columnas.

Direccin de memoria = Direccin inicial en la memoria + (Numero de renglones del arreglo *


Posicin del arreglo en columna o ndice de columna + Posicin del arreglo en rengln o ndice
de rengln) * Tamao en bytes del tipo de dato primitivo

Ejemplo. Tomamos como base el ejemplo anterior, pero ahora con columnas quedara de la
siguiente manera:

arreglo

direcci
n

40

50

60

270
0

271
2

272
4

70

80

90

270
4

271
6

272
8

70

80

90

270
8

272
0

273
2

arreglo[0][0] = 2700 + (3 * 0 + 0) * 4 = 2700


arreglo[1][0] = 2700 + (3 * 0 + 1) * 4 = 2704
arreglo[2][0] = 2700 + (3 * 0 + 2) * 4 = 2708
arreglo[0][1] = 2700 + (3 * 1 + 0) * 4 = 2712
arreglo[1][1] = 2700 + (3 * 1 + 1) * 4 = 2716
arreglo[2][1] = 2700 + (3 * 1 + 2) * 4 = 2720
arreglo[0][2] = 2700 + (3 * 2 + 0) * 4 = 2724
arreglo[1][2] = 2700 + (3 * 2 + 1) * 4 = 2728

arreglo[2][2] = 2700 + (3 * 2 + 2) * 4 = 2732

Con los arreglos unidimensionales o bidimensionales se pueden desarrollar una serie de


operaciones tales como:

Lectura / Escritura.
Asignacin.
Actualizacin:
Insercin.

Eliminacin.

Modificacin.
Ordenamiento.
Bsqueda.

De las operaciones anteriores la insercin, eliminacin y modificacin son operaciones que


solo se aplican a un elemento y no a todos de forma global.

1.4.2 Manejo de memoria dinmica

En la memoria dinmica durante la ejecucin del programa el tamao de la estructura puede


cambiar.
La memoria dinmica, es el espacio de almacenamiento que solicita una clase o mtodo en
tiempo de ejecucin. De esa manera, a medida que el proceso requiere de ms espacio se
solicita al sistema operativo, sin que el proceso se preocupe por donde sern asignados los
datos, ni que espacios de memoria nos entregara el sistema operativo.
As como existen estructuras de datos estticas (arreglos), tambin existen estructuras de
datos dinmicas (listas y rboles), estas ltimas son generadas a partir de un tipo de dato
conocido como referencia (direccin de memoria). Para utilizar las referencias se requiere de
un elemento llamado nodo, el cual se estructura de la siguiente manera.

Dato

Dir

Dir

Nodo con una referencia

Dato

Dir

Nodo con dos referencias

Las estructuras de datos que usan nodos pueden ser lineales o no lineales, dentro de las
lineales se encuentran las listas simples y dobles y en las no lineales encontramos los rboles,
estas estructuras se representan de la siguiente forma.

Lista simple.
------>
Dato

Dir

----->

Dato

Dir

Dato

Dir

Lista doble
--->
Dir

Dato

Dir

---->

Dir

Dato

Dir

Dir

Dato

<--<-----

rbol
Dir

Dato

Dir

Dato

Dir

Dir

Dir

Dato

Dir

Dir

NOTA: La direccin de un nodo apunta al dato del


siguiente nodo, lo mismo sucede en la forma inversa.
Suponga que despus de las declaraciones, observamos que necesitaremos 12 valores de tipo
int en lugar de 10. En tal caso podemos emplear la siguiente estrategia:
int original[ ]=a;
a=new int [12];
for(int i=0;i<10;i++)
a[i]=original[i];
1. Implementer una clase de operaciones de vectores que muestre la utilizacin de meoria
dinamica
class OperVectores{
int arr[];
public OperVectores();
public OperVectores(int tam);
public void LlenarAleatoria();
public void Mostrar();
public void pares();
public void May50();
public void asignar(int n);
public void asignar (int t[]);
}
Vector
Implementa un arreglo dinmico de objetos, es decir proporciona las capacidades de las

estructuras de datos de tipo arreglo, que puede cambiar su propio tamao de forma dinmica.
En cualquier momento, un objeto Vector contiene un nmero de elementos que es menor o
igual que su capacidad. La capacidad es el espacio que se a reservado para los elementos de
Vector. Si un objeto Vector requiere de una capacidad adicional, crece en base a un incremento
de capacidad que usted le especifica, o en base a un incremento de capacidad
predeterminado. Si usted no especifica un incremento de capacidad, el sistema duplicara el
tamao de un objeto Vector cada vez que se requiera de una capacidad adicional.
Los constructores de la clase Vector son los siguientes:

Vector( )

Vector(int tamao)

Vector(int tamao,int incr)


a) Esta es la primera forma que crea un vector por defecto, que tiene un tamao inicial
de 10.
b) Esta es la segunda opcin que crea un vector que viene especificado por tamao.
c) La tercera forma crea un vector de capacidad especificada en tamao e incremento
incr. El incremento indica el nmero de elementos cuya memoria se asigna cada vez
que el vector se aumenta de tamao.

Los objetos Vector almacenan referencias a objetos Object. Por lo tanto, un prograna puede
almacenar referencias a cualquier objeto en un objeto Vector. Para almacenar valores de tipos
primitivos en objetos Vector, utilice las clases de tipo de envoltura (por ejemplo, Integer y
Double) del paquete java.lang para crear objetos que contengan los valores de tipo primitivo.

Mtodos definidos por Vector

Mtodo

Descripcin

Final void addElement(Object El objeto especificado por elemento se aade al vector.


elemento)
Int capacity( )

Devuelve la capacidad del vector

Object clone( )

Devuelve un duplicado del vector invocante.

Bolean
elemento)

contains(Object Devuelve true si elemento est contenido en el vector, y


devuelve false si no.

Void
matriz[ ])

copyInto(Object Los elementos contenidos en el vector invocante se copian en


la matriz especificada por matriz.

Object elementAt(int indice)

Devuelve el elemento en la posicin indicada por ndice.

Enumeration elements( )

Devuelve una enumeracin de los elementos del vector.

Void
tamano)

ensureCapacity(int Establece la mnima capacidad del vector en tamao.

Object firstElement( )

Devuelve el primer elemento del vector

Int indexOf(Object elemento)

Devuelve el ndice de la primera aparicin de elemento. Si el


objeto no est en el vector, se devuelve 1.

Int indexOf(Object elemento, Devuelve el ndice de la primera aparicin de elemento en o


int inicio)
despus de inicio. Si el vector no est en esa parte del vector,
se devuelve 1.
Void insertElementAt(Object Aade elemento al vector en la posicin indicada por ndice.
elemento,int indice)

Bolean isEmpty( )

Devuelve true si el vector esta vaci y false si contiene uno o


ms elementos.

Object lastElement( )

Devuelve el ltimo elemento del vector.

Int
lastIndexOf(Object Devuelve el ndice de la ltima aparicin de elemento, si el
elemento)
elemento no est en el vector, se devuelve 1.
Int
lastIndexOf(Object Devuelve el ndice de la ltima aparicin antes de inicio. Si el
elemento,int inicio)
objeto no est en esa parte del vector, se devuelve 1.
Void removeAllElements( )

Vaca el vector. Tras ejecutarse este mtodo, el tamao del


vector es 0.

Bolean
removeElement(Object
elemento)

Quita elemento del vector. Si existe en el vector ms de una


instancia del objeto especificado, entonces se quita la primera.
Devuelve true si se hizo con xito y false si el objeto no se
encontr.

void
indice)

removeElementAt(int Quita el elemento en la posicin indicada por ndice.

Void
setElementAt(Object Se asigna elemento a la posicin indicada por ndice.
elemento,int indice)
Void setSize(int tamano)

Establece el nmero de elementos en el vector en tamao. Si


el nuevo nmero es menor que el viejo, se pierden elementos.
Si el nuevo tamao es mayor que el viejo, se aaden
elementos null.

Int size( )

Devuelve el nmero de elementos actualmente en el vector.

String toString( )

Devuelve la cadena equivalente del vector.

Void trimToSize( )

Establece la capacidad del vector para que sea igual al

nmero de elementos que contiene actualmente.

1.5 Anlisis de algoritmos.


Un algoritmo es una secuencia de pasos lgica para encontrar la solucin de un
problema.
Todo algoritmo debe contar con las siguientes caractersticas:
Preciso, definido y finito.
Por Preciso, entenderemos que cada paso del algoritmo tiene una relacin con el
anterior y el siguiente;
Un algoritmo es Definido, cuando se ejecuta ms de una vez con los mismos datos
y el resultado es el mismo;
Finito, indica que el algoritmo cuenta con una serie de pasos definidos o que tiene
un fin.
Hablando de estructuras de datos podemos decir que los algoritmos segn su
funcin se dividen en:

Algoritmos de ordenamiento

Algoritmos de bsqueda.

Un algoritmo de ordenamiento, es el que pone los elementos de una lista o vector


en una secuencia (ascendente o descendente) diferente a la entrada, es decir, el
resultado de salida debe ser una permutacin (reordenamiento) de la entrada que
satisfaga la relacin de orden requerida.
Un algoritmo de bsqueda, es aquel que est diseado para encontrar la solucin
de un problema booleano de existencia o no de un elemento en particular dentro de
un conjunto finito de elementos (estructura de datos), es decir al finalizar el algoritmo
este debe decir si el elemento en cuestin existe o no en ese conjunto, adems, en
caso de existir, el algoritmo podra proporcionar la localizacin del elemento dentro
del conjunto.
Concepto de complejidad de algoritmos.
La mayora de los problemas que se plantean en la actualidad se pueden resolver

con algoritmos que difieren en su eficiencia. Dicha diferencia puede ser irrelevante
cuando el nmero de datos es pequeo pero cuando la cantidad de datos es mayor
la diferencia crece. Ejemplo: Suma de 4 y 10 primero nmeros naturales.

1+2+3+4 = 10 3

3 4*(4+1)/2 = 10
tiempo

1+2+3+4+5+6+7+8+9+10 = 55 9

3 10*(10+1)/2 = 55

La complejidad de un algoritmo o complejidad computacional, estudia los recursos y


esfuerzos requeridos durante el clculo para resolver un problema los cuales se
dividen en: tiempo de ejecucin y espacio en memoria. El factor tiempo, por lo
general es ms importante que el factor espacio, pero existen algoritmos que
ofrecen el peor de los casos en un menor tiempo que el mejor de los casos, lo cual
no es la mejor de las soluciones.
El factor tiempo de ejecucin de un algoritmo depende de la cantidad de datos que
se quieren procesar, una forma de apreciar esto es con las cuatro curvas que se
presentan en las graficas de la figura 1.1.

Como se puede apreciar en las graficas, entre mayor se al nmero de datos mayor
tiempo se aplica en las graficas a), b) y c), lo cual no ocurre con la grafica d), por lo
tanto podemos deducir que una funcin que se acerque ms al eje de las x es ms
constante y eficiente en le manejo de grandes cantidades de datos.
Aritmtica de la notacin O.
La notacin asinttica O (grande) se utiliza para hacer referencia a la velocidad de
crecimiento de los valores de una funcin, es decir, su utilidad radica en encontrar un
lmite superior del tiempo de ejecucin de un algoritmo buscando el peor caso.
La definicin de esta notacin es la siguiente:
f(n) y g(n) funciones que representan enteros no negativos a nmeros reales. Se
dice que f(n) es O(g(n)) si y solo si hay una constante realc>0 y un entero
constante n0>=1 tal que f(n)<=cg(n) para todo entero n>= n0. Esta definicin se
ilustra en la figura 1.2.

Nota: el orden de magnitud de una funcin es el orden del trmino de la funcin


ms grande respecto de n.
La notacin asinttica O grande se utiliza para especificar una cota inferior para la

velocidad de crecimiento de T(n), y significa que existe una constante c tal que T(n)
es mayor o igual a c(g(n)) para un nmero infinito de valores n.
Regla para la notacin O

Definicin (O)T(n) es O(f(n)) si existen las constantes positivas c y


n0 tales que n >= n0 se verifica T(n)<= cf(n).

Esta definicin afirma que existe un punto inicial n 0 tal que para todos los valores de
n despus de ese punto; el tiempo de ejecucin T(n) esta acotado por algn mltiplo
de f(n).
La expresin matemtica de lo anterior es T(n) = O(f(n)) y el ndice de crecimiento
de T(n) es <= al crecimiento de f(n).
T(n) Tiempo de ejecucin del algoritmo.
F(n) Tiempo al introducir los datos al algoritmo.

1.5.1 Complejidad en el Tiempo de ejecucin de un algoritmo.


El tiempo de ejecucin de un algoritmo, se refiere a la suma de los tiempos en los
que el programa tarda en ejecutar una a una todas sus instrucciones, tomando en
cuanta que cada instruccin requiere una unidad de tiempo, dicho tiempo se puede
calcular en funcin de n (el numero de datos), lo que se denomina T(n)
Si hacemos un anlisis de forma directa al programa para determinar el tiempo de
ejecucin del mismo, debemos definir el conjunto deoperaciones primitivas, que son
independientes del lenguaje de programacin que se use. Algunas de las funciones
primitivas son las siguientes:
-

Asignacin de un valor a una variable.

Llamada a un mtodo.

Ejecucin de una operacin aritmtica.

Comparar dos nmeros.

Poner ndices a un arreglo.

Seguir una referencia de objeto.

Retorno de un mtodo.

En forma especfica, una operacin primitiva corresponde a una instruccin en el


lenguaje de bajo nivel, cuyo tiempo de ejecucin depende del ambiente de hardware
y software, pero es constante. Ejemplo. Mtodo que retorna el nmero mayor de un
arreglo de n elementos.
public int Mayor()
{
int may=arr[0];
for(ind=0; ind<arr.length; ind++)
if(arr[ind]>may)
may=arr[ind];
return may;
}

Para este ejemplo se pueden encontrar dos formulas que determinen el tiempo de
ejecucin, la primera representa el peor de los casos y la segunda el mejor de los
casos. Para se creacin se sigue el programa:

La inicializacin de la variable may=arr[0], corresponde a dos unidades


de tiempo.

La inicializacin del ciclo for agrega otra unidad de tiempo.

La condicin del ciclo for se ejecuta desde 1 hasta el tamao del


arreglo lo cual agrega el nmero de unidades del tamao del arreglo.

El cuerpo del ciclo for se ejecuta el tamao del arreglo - 1 veces, para
este caso el numero de operaciones del cuerpo del ciclo pueden ser 6
o 4 (condicin del if dos, asignacin a may dos e incremento y
asignacin dos) en el peor o mejor de los casos respectivamente. Por
consiguiente el cuerpo del ciclo contribuye con 4(tamao del arreglo 1) o 6(tamao del arreglo - 1) unidades de tiempo.

Y el retorno de may aporta una unidad de tiempo.

Con todo lo anterior se logra obtener las siguientes formulas (tamao del arreglo o
arr.length se cambian por n):

T(n) = 2+1+n+6(n-1)+1 = 7n-2

Peor de los casos.

T(n) = 2+1+n+4(n-1)+1 = 5n

Mejor de los casos.

1.5.2 Complejidad en espacio.


La complejidad de espacio, se refiere a la memoria que utiliza un programa para su
ejecucin; es decir el espacio de memoria que ocupan todas las variables propias
del programa. Dicha memoria se divide en Memoria esttica y Memoria dinmica.
Para calcular la memoria esttica, se suman la cantidad de memoria que ocupa

cada una de las variables declaradas en el programa.


Tomando en cuenta los tipos de datos primitivos del lenguaje de programacin java
podemos determinar el espacio que requiere cada una de las variables de un
programa, de acuerdo a lo siguiente:

Tipo de dato primitivo

Tamao en bits

Tamao en Bytes

byte

char

16

short

16

int

32

float

32

long

64

double

64

El clculo de la memoria dinmica, no es tan simple ya que depende de cada


ejecucin del programa o algoritmo y el tipo de estructuras dinmicas que se estn
utilizando.

1.5.3 Eficiencia de Algoritmos


Una de las caractersticas primordiales en la seleccin de un algoritmo es que este

sea sencillo de entender, calcular, codificar y depurar, as mismo que utilice


eficientemente los recursos de la computadora y se ejecute con la mayor rapidez
posible con un eficaz uso de memoria dinmica y esttica.
Tambin para seleccionar correctamente el mejor algoritmo es necesario realizar
estas preguntas:
Qu grado de orden tendr la informacin que vas a manejar?
Si la informacin va a estar casi ordenada y no quieres complicarte, un algoritmo
sencillo como el ordenamiento burbuja ser suficiente. Si por el contrario los datos
van a estar muy desordenados, un algoritmo poderoso como Quicksort puede ser el
ms indicado. Y si no puedes hacer una presuncin sobre el grado de orden de la
informacin, lo mejor ser elegir un algoritmo que se comporte de manera similar en
cualquiera de estos dos casos extremos.
Qu cantidad de datos vas a manipular?
Si la cantidad es pequea, no es necesario utilizar un algoritmo complejo, y es
preferible uno de fcil implementacin. Una cantidad muy grande puede hacer
prohibitivo utilizar un algoritmo que requiera de mucha memoria adicional.
Qu tipo de datos quieres ordenar?
Algunos algoritmos slo funcionan con un tipo especfico de datos (enteros, enteros
positivos, etc.) y otros son generales, es decir, aplicables a cualquier tipo de dato.
Qu tamao tienen los registros de tu lista?
Algunos algoritmos realizan mltiples intercambios (burbuja, insercin). Si los
registros son de gran tamao estos intercambios son ms lentos.

Unidad II Recursividad
2.1 Definicion de Metodos Recursivos

Recursin es una tcnica de programacin en el cual un mtodo puede llamarse a s


mismo. La recursin es muy interesante y una tcnica efectiva en programacin ya
que puede producir algoritmos cortos y eficientes.
Algo es recursivo si se define en trminos de s mismo (cuando para definirse hace
mencin a s mismo).
Si la invocacin de un subprograma (funcin o subrutina) se produce desde el
propio subprograma se dice que se trata de un subprograma recursivo.
Un mtodo recursivo es un mtodo, directa o indirectamente, se hace una llamada a
s mismo.
La recursin consiste en el uso de mtodos recursivos.

Se dice que una funcin es recursiva cuando dicha funcin se define en trminos
de la misma funcin. Es importante recordar que no todas la funciones pueden
llamarse a si mismas, deben estar diseadas especialmente para comportarse de
manera recursiva, de otro modo dichas funciones podran conducir a bucles
infinitos, o a que el programa termine inadecuadamente.
Por ejemplo, podramos pensar en la creacin de un algoritmo que produzca el
resultado de un numero factorial, la definicin iterativa de dicho algoritmo seria la
siguiente:
int factorial (int n)
{
prod = 1;
for ( x = n; x > 0; x-- )
prod *= x;
return prod;
}

La definicin iterativa es sencilla, pero no resulta tan intuitiva, en cambio, uno


podra analizar mas detalladamente el problema y generar una definicin recursiva
para una funcin factorial que podra resultar mas intuitiva y sencilla de
implementar:
n! = n * (n-1)!
!0 = 1

Podemos observar en dicha definicin que el factorial de un numero es el producto


de dicho numero por el factorial del numero entero menor prximo y as
consecutivamente, adems podemos ver que el factorial de el numero cero es 1, lo
que nos indica un caso de base o un caso de parada que ya no requiere mas
llamadas consecutivas. A partir de este caso base se realiza un retroceso en las
llamadas que obtiene como resultado el producto del caso base por cada uno de
los valores n del numero en las llamadas.
1

3! = 3 * 2!

2
3
4
3
2
1

2! = 2 * 1!

1! = 1 * 0!
1! = 1 * 1

3! = 3 * 2
6

0! = 1

2! = 2 * 1

Podemos observar que cada caso es reducido a un caso simple hasta que se
alcanza el caso base de 0!, el cual es definido directamente como 1. En la lnea 4
obtenemos un valor que esta definido directamente y no se realiza el factorial de
ningn otro numero. De esta manera podemos realizar un retro seguimiento
(backtrack) de la lnea 4 a la 1, regresando el valor calculado en cada lnea
evaluando el resultado de las lneas previas. Analizando lo anterior concluimos que
en una definicin recursiva las instancias complejas de un proceso se define en
trminos de instancias mas simples, estando stas definidas en forma explicita,
facilitando el diseo de la funcin.

2.1.1 Propiedades de los algoritmos recursivos


La idea bsica de la recursividad es definir una funcin en trminos de si misma y
reducirla hasta alcanzar un caso nico (caso base) en el que los trminos no
involucran a la funcin, y as permita

detener las llamadas recursivas y recolectar el valor en cada retroceso auxilindose


de una pila para conservar los valores indispensables. En el caso del factorial los
nmeros n fueron almacenados en una pila en cada llamada.
Sin una salida no recursiva, la funcin recursiva no podra ser calculada. Alguna
instancia del la definicin recursiva debe eventualmente reducirse a la
manipulacin de uno o mas casos simples no recursivos.

2.2 Funcionamiento interno de la recursividad


Cuando se llama a una funcin recursiva, se crea un nuevo juego de variables
locales, de este modo, si la funcin hace una llamada a si misma, se guardan sus
variables y parmetros en una pila de datos, y la nueva instancia de la funcin
trabajar con su propia copia de las variables locales, cuando esta segunda
instancia de la funcin retorna, recupera las variables y los parmetros de la pila y
continua la ejecucin en el punto en que haba sido llamada.
La mayora de los lenguajes de alto nivel como C, C++ o Java permiten la
construccin de subrutinas, funciones que pueden ser llamadas a si mismas
(recursivas) de manera natural. A continuacin se muestra una funcin recursiva
que calcula el factorial de un numero n:
int factorial( int n )
{
if ( n == 0 )
return 1;
else return n * factorial( n-- );
}

Veamos paso a paso que es lo que ocurre cuando realizamos una llamada a dicha
funcin con un valor de n igual a 3, factorial(3):
llama
llama
llama
llama
llamada
llamada
llamada

1 n
2 n
3 n
4 n
3 n
2 n
1 n

=
=
=
=
=
=
=

3
2
1
0
1
2
3

n
n
n
n

!
!
!
=

0
0
0
0

n * factorial(2)
n * factorial(1)
n * factorial(0)
return 1 1(regresa a
return n * 1 1 (regresa a 2)
return n * 1 2 (regresa a 1)
return n * 2 6 (termina)

Podemos observar que en cada llamada de funcin el valor de n es conservado en


cada llamada a funcin, en realidad el valor de n es colocado en una pila y
posteriormente es recuperado cada vez que regresa a la llamada de cada funcin
factorial.

2.2.1 Ventajas y desventajas de la recursividad


Las funciones recursivas son mecanismo muy eficientes de programacin pues
facilitan el diseo de las mismas, sin embargo la recursividad no es en todos los

casos un buen modo de resolver problemas, ya que existen casos en los que un
algoritmo iterativo resolvera de manera bastante adecuada un problema
determinado.
La recursividad consume recursos adicionales de memoria y tiempo de ejecucin,
y se debe aplicar a funciones que realmente obtengan beneficio directo. Para el
caso de la funcin factorial parece ser una buena medida el empleo de
recursividad, sin embargo analizaremos ahora el caso de una funcin que genera
una secuencia de nmeros a la que se le conoce como serie de fibonacci.

Consideraciones sobre procedimientos recursivos Condiciones de limitacin. Debe


designar un procedimiento recursivo para probar al menos una condicin que pueda
poner fin a la recursividad; tambin debe supervisar los casos en los que no se
satisface ninguna condicin dentro de un nmero razonable de llamadas recursivas.
Si no existe al menos una condicin que pueda cumplirse sin errores, el
procedimiento corre unriesgo elevado de ejecutarse en un bucle infinito. Uso de la
memoria. La aplicacin tiene una cantidad de espacio limitada para
las variables locales. Cada vez que un procedimiento se llama a s mismo, utiliza ms
cantidad de ese espacio para las copias adicionales de sus variables locales. Si
este proceso contina indefinidamente, se acaba produciendo un error Stack
Overflow Exception?.

Eficacia. Casi siempre se puede sustituir un bucle por la recursividad. Un bucle no


tiene la sobrecarga de transferir argumentos, inicializar el almacenamiento adicional y
devolver valores. Su rendimiento puede ser mucho mayor sin llamadas recursivas.
Recursividad mutua. Si dos procedimientos se llaman mutuamente, el rendimiento
puede ser muy deficiente o incluso puede producirse un bucle infinito. Este tipo
de diseopresenta los mismos problemas que un procedimiento recursivo nico, pero
puede ser ms difcil de detectar y depurar. Llamadas con parntesis. Cuando un
procedimiento se llama a s mismo de manera recursiva, debe agregar parntesis
detrs del nombre del procedimiento, aun cuando no exista una lista de argumentos.
De lo contrario, se considerar que el nombre de la funcin representa
al valor devuelto por sta. PruebasSi escribe un procedimiento recursivo, debe
probarlo minuciosamente para asegurarse de que siempre cumple ciertas
condiciones de limitacin. Tambin debera comprobar que la memoria no resulta
insuficiente
debido
a
la
gran
cantidad
de
llamadas
recursivas.

MECANICA DE RECURSION. Es mucho mas difcil desarrollar una solucin


recursiva para resolver un problema especifico cuando no se tiene un algoritmo. No
es solo el programa sino las definiciones originales y los algoritmos los que deben
desarrollarse. En general, cuando encaramos la tarea de escribir un programa para
resolver un problema no hay razn para buscar una solucin recursiva. La mayora
de los problemas pueden resolverse de una manera directa usando mtodos no
recursivos. Sin embargo, otros pueden resolverse de una manera mas lgica y
elegante mediante la recursin. Volviendo a examinar la funcin factorial. El factor es,
probablemente, un ejemplo fundamental de un problema que no debe resolverse de
manera recursiva, dado que su solucin iterativa es directa y simple. Sin embargo,
examinaremos los elementos que permiten dar una solucin recursiva. Antes que

nada, puede reconocerse un gran nmero de casos distintos que se deben resolver.
Es decir, quiere escribirse un programa para calcular 0!, 1!, 2! Y as sucesivamente.
Puede identificarse un caso trivial para el cual la solucin no recursiva pueda
obtenerse en forma directa. Es el caso de 0!, que se define como 1. El siguiente paso
es encontrar un mtodo para resolver un caso complejo en trminos de uno mas
simple, lo cual permite la reduccin de un problema complejo a uno mas simple. La
transformacin del caso complejo al simple resultara al final en el caso trivial. Esto
significara que el caso complejo se define, en lo fundamental, en trminos del mas
simple.

Consideraciones de la Recursividad

Los parmetros y variables locales toman nuevos valores en cada llamada (no se
trabaja con los anteriores).

Cada vez que se llama un mtodo, el valor de los parmetros y variables locales se
almacenan en la pila de ejecucin. Cuando termina la ejecucin se recuperan los
valores de la activacin anterior.

El espacio necesario para almacenar los valores en memoria (pila) crece en funcin
de las llamadas.

Con cada llamada recursiva se crea una copia de todas las variables y constantes
que estn vigentes, y se guarda esa copia en la pila.

Adems se guarda una referencia a la siguiente instruccin a ejecutar.

Al regresar se toma la imagen guardada (registro de activacin) en el tope de la pila


y se contina operando. Esta accin se repite hasta que la pila quede vaca.
Consideraciones
de
la
recursividad

La serie de fibonacci es una secuencia de nmeros enteros en donde cada elemento de la


secuencia es la suma de los dos elementos que la preceden:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34,

El calculo de un numero para la secuencia de fibonacci puede ser definida de la siguiente


manera recursiva:
fib(n) = n si n == 0 o n == 1
fib(n) = fib(n-2) + fib(n-1) si n >= 2

Para calcular fib(4), por ejemplo, podramos aplicar la siguiente definicin recursiva para
obtenerla:
fib(4)=

fib(0)+
0
+
0
+

fib(1)
1
1

+
+
+
+

fib(3
fib(1
1
1

fib(2)
fib(0) +
0
+
3

fib(1)
1
=

Podemos observar que dentro de cada definicin recursiva es necesario realizar


nuevamente un llamado recursivo doble, y esto claramente es un inconveniente
innecesario, pues es superfluo ocupar demasiados recursos computacionales para poder
realizar el calculo, en este caso un algoritmo iterativo realizara la misma operacin, pero
de manera mas eficiente.
int fibonacci( int n ) {
if (n <= 1)
return n;
anterior = 0;
actual = 1;
for (i = 2; i <= n; i ++) {
x = anterior
anterior = actual;
actual = x + anterior
}
return actual;
}

Es claro darse cuenta que un algoritmo iterativo para el caso de la serie de fibonacci
resulta un mtodo menos costoso por la cantidad de recursos computacionales que se
necesitan emplear.

2.2 Procedimientos recursivos


Los procedimientos recursivos o recurrentes se pueden clasificar en dos formas
distintas:
-

Recursividad directa o
Recursividad indirecta

La recursividad directa se presenta cuando el mtodo se manda llamar a s mismo


dentro de su propio cuerpo de instrucciones.

public int Metodo(int n)


{
:
n = Metodo(n-1);
}
La recursividad indirecta se manifiesta cundo un mtodo llama a otro y dentro del
segundo se manda llamar al primero. O cuando existe la llamada a mtodos de
forma encadenada y al terminar el ltimo mtodo llamado, transfiere el control al
anterior, hasta llegar al mtodo que inicio la serie de llamadas.
public int Metodo1(int n)
{
:
n = Metodo2(n-1);
}
public int Metodo2(int n)
{
:
n = Metodo1(n-1);
}
Analizando el concepto de recursividad y su clasificacin, puede indicar que es un
procedimiento infinito, que solo se detendr en el momento que se agote la memoria,
generando un error de programacin y la interrupcin del mismo.
Pero esto no es as, ya que debe existir un elemento que indica el retorno de un
resultado concreto y no el retorno de la llamada al mtodo recursivo o recurrente.

Forma de generar la recursividad


Un problema clsico: El factorial de un nmero.
Todos conocemos que la definicin matemtica del factorial de un nmero n se
obtiene de la siguiente forma:
n! = 1*2*3*.......*n n! = n*(n-1)*(n-2)*.....1
Por definicin matemtica, tambin sabemos que:
0! = 1
Con lo que tendramos la siguiente definicin de factorial:

n!

1
n*(n-1)

Si n=0

Caso Base

Otros casos

Parte Recursiva

Un problema que puede resolverse de manera recursiva, debe tener por lo menos
caso base y 1 parte recursiva, sino no hay recursin.
Ahora, para implementarlo en un lenguaje de programacin como Java, solo
tenemos que traducir nuestros casos bases y partes recursivas:

Funcionamiento del proceso


n

Llamado a factorial

4*factorial(3)

3
2
1

3*factorial(2)
2*factorial(1)
1*factorial(0)

En general el proceso es (4*factorial(3*factorial(2*factorial(1*factorial(0)))))


Realizar de manera recursiva la sumatoria de n nmeros naturales de manera
recursiva.
La sumatoria de n nmeros se realiza de la siguiente forma:
n=10
1+2+3+4+5+6+7+8+9+10 = 10+9+8+7+6+5+4+3+2+1

De tal forma que podemos determinar que:


1

si n = 1

paso bsico

n + (n-1)

si n > 1

paso inductivo o proceso recursivo

//5+suma(4+suma(3+suma(2+suma(1))))

Imprimir de manera recursiva la serie de fibonnaci


La serie de fibonacci trabaja de la siguiente forma:
Paso 1: Si tenemos dos semillas formadas por s1=0 y s1=1
01
Paso 2. Se suman para obtener el resultado
011
Ahora la semilla 1 se queda con el valor de la semilla 2 y el resultado se asigna a la
semilla 2 y se vuelve a realizar el paso 2. De tal forma que la serie queda de la
siguiente forma:
0 1 1 2 3 5 8 13 21 34 55 89 .

2.3 Ejemplos de Recursividad

A continuacin se muestran algunos ejemplos de problemas cotidianos que


pueden ser fcilmente replanteados en trminos de recursividad, en primer lugar
trataremos algunos ejemplos de operaciones recursivas con vectores y al final un
ejemplo de diseo de permutaciones recursivas.

Bsqueda Binaria : Dentro de los mtodos de bsqueda de elementos, la


bsqueda binaria es uno de los mas apropiados cuando hablamos de datos que
ya se encuentran ordenados. Se basa en el fundamento de divide y vencers para
localizar el elemento deseado. Es un proceso muy similar al que una persona
utiliza para buscar informacin en un entorno ordenado, por ejemplo en una
agenda, si uno abre la agenda en la parte central, por ejemplo en la M, y la letra a
buscar es la J, pues nicamente se desplaza al inicio de la agenda que es la
direccin correcta.
El algoritmo de bsqueda binaria comienza en el elemento central de la lista. Si el
elemento central no es buscado, entonces se repite la bsqueda, centrndose en
la primera o segunda mitad, dependiendo de que el elemento buscado sea ms
pequeo o ms grande que el valor central.

La idea para implantar este mismo mecanismo en un proceso recursivo nos lleva a
la necesidad de pensar una funcin que sea capas de recibir el arreglo en el que
estamos buscando, el dato a buscar y los limites de cada sub-arreglo que vamos
generando tras el proceso de bsqueda.
El resultado es el siguiente algoritmo recursivo, una funcin llamada binsearch que
es capas de recibir como argumentos los datos antes mencionados, tras cada
llamada se va seleccionando un sub-arreglo dentro del arreglo, donde se localizar
el elemento:
int binsearch(int x, int* array, int bajo, int alto)
{
int mid = 0;
if ( bajo > alto )
return -1;
mid = (int)(bajo + alto)/2; // rendea al menor
if ( x == array[mid])
return mid;
else if ( x < array[mid] )
binsearch(x, array, bajo, mid-1);
else
binsearch(x, array, mid+1, alto);
}

Suma de elementos de un arreglo: La idea de este algoritmo es realizar la suma


de todos los elementos de un arreglo de manera recursiva, para ello es necesario
encontrar el primer elemento del arreglo y a partir de l de manera recursiva
hacemos la recuperacin de cada casilla del arreglo de manera que vamos
sumando cada elemento del arreglo hasta llegar al ultimo elemento del arreglo
representado por n.
int suma(int* array, int n)
{
if (n == 0)
return array[0];
else
return array[n] + suma(array, n-1);
}

Mximo elemento en un arreglo: La idea bsica en este algoritmo es encontrar el


nmero de mayor valor dentro de un arreglo de elementos, para lograrlo
recorreremos todo el arreglo de fin a inicio de manera recursiva y en cada
retroceso compararemos el valor actual de la llamada con el resultado anterior a
ella:
int maximo(int* array, int n)
{
if (n == 0)
return array[0];
else {
int y = maximo(array, n-1);
return ( array[n] > y ? array [n]: y );
}
}

Permutaciones: El siguiente algoritmo resulta muy interesante pues se trata de

construir permutaciones de elementos de manera recursiva. Las permutaciones de


un conjunto son las diferentes maneras de colocar sus elementos, usando todos
ellos y sin repetir ninguno.

Por ejemplo para A, B, C, tenemos: { ABC, ACB, BAC, BCA, CAB, CBA } como el conjunto de
permutaciones posibles.
/* Prototipo de funcin */
void Permutaciones(char *, int l=0);
int main(int argc, char *argv[])
{ char palabra[] = "ABCDE";
Permutaciones(palabra);
cin.get(); return
0;
}
void Permutaciones(char * cad, int l) {
char c;
/* variable auxiliar para intercambio */
int i, j; /* variables para bucles */
int n = strlen(cad);

for(i = 0; i < n-l; i++) {


if(n-l > 2) Permutaciones(cad, l+1);
else cout << cad << ", ";
/* Intercambio de posiciones */ c
= cad[l];
cad[l] = cad[l+i+1];
cad[l+i+1] = c; if(l+i
== n-1) {
for(j = l; j < n; j++) cad[j] = cad[j+1];
cad[n] = 0;
}
}

La mayora de los procesos recursivos bien diseados suelen ser muy eficientes y
en contadas ocasiones fallan, pero es importante recordar no todos los procesos
recursivos son eficientes por naturaleza propia.
Fibonnacci de manera recursiva
Fibonacci(0,1,21)=1
Fibonacci(1,1,21)=2
Fibonacci(1,2,21)=3
Fibonacci(2,3,21)=5
Fibonacci(3,5,21)=8
Fibonacci(5,8,21)=13
Fibonacci(8,13,21)=21

Realizar de manera recursiva la potencia de un nmero para n.


El clculo de la potencia de un nmero se resuelve de la siguiente manera.
Xp=X*X(p-1)*X(p-2)*X(p-3)= 25= 2 * 2 * 2 * 2 * 2
Sabemos que 20= 1 por lo que determinamos

1
n * (n, p-1)

si p = 0
si p > 0

paso bsico
paso inductivo

24 2*potencia(2*potencia(2*potencia(2*potencia(2,0))
Potencia(2,4)=2*potencia(2,3)=16
Potencia(2,3)=2*potencia(2,2)=8
Potencia(2,2)=2*potencia(2,1)=4
Potencia(2,1)=2*potencia(2,0)=2
Potencia(2,0)=1

Realizar el producto de un nmero por sumas sucesivas de manera recursiva.


El producto de un nmero (3*9) se realiza de la siguiente forma:
1 2 3 4 5 6 7 8 9

3+3+3+3+3+3+3+3+3

cant =0
n+(cant-1)

caso base
proceso recursivo

Realizar de manera recursiva como calcular un nmero en binario a travs


de desplazamientos sucesivos

La manera de convertir a binario un nmero es de la siguiente forma:


Si es para un nmero de ocho bits el rango de valores abarca de 0 a 255, si es en ese
caso se debe de utilizar una mscara de 0x80. Que representa a un nmero en
binario 10000000 es decir el ltimo bit en 1.
Se realiza una operacin (AND)& a nivel de bits del nmero a convertir y si el resultado
es cero entonces ese representa al primer valor del nmero en binario y si no es cero
entonces este lo representa.
Posterior a esto se desplaza el bit una posicin a la derecha y se vuelve a hacer la
misma operacin & y nos vuelve a dar como resultado 0 o un numero diferente de 0.
Este proceso se repite hasta que se llegue hasta el final # de bits. En este caso 8
iteraciones.
Ejemplo:
Numero a convertir es 13
La mscara es 0x80=10000000

13 en binario es 00001101
La mscara es 10000000
Resultado &

00000000

da el primer valor del nmero convertido 0

Se repite el mismo proceso pero con el bit 1 de la mscara recorrido una posicin a la
derecha.

13 en binario es 00001101
La mscara es 01000000
Resultado &

00000000

da el primer valor del nmero convertido 0

13 en binario es 00001101
La mscara es 00100000
Resultado &

00000000

13 en binario es 00001101

da el primer valor del nmero convertido 0

La mscara es 00010000
Resultado &

00000000

da el primer valor del nmero convertido 0

13 en binario es 00001101
La mscara es 00001000
Resultado &

00001000

da el primer valor del nmero convertido 1

13 en binario es 00001101
La mscara es 00000100
Resultado &

00000100

da el primer valor del nmero convertido 1

13 en binario es 00001101
La mscara es 00000010
Resultado &

00000000

da el primer valor del nmero convertido 0

13 en binario es 00001101
La mscara es 00000001
Resultado &

00000000

da el primer valor del nmero convertido 1

Algunos problemas de ejemplos:

Problema 1:
Implementacin de un mtodo recursivo.
Programa:

publicclassRecursividad{
voidrepetir(){
repetir();
}

publicstaticvoidmain(String[]ar){

Recursividadre=newRecursividad();
re.repetir();
}
}
La funcin repetir es recursiva porque dentro de la funcin se llama a s misma.
Cuando ejecuta este programa se bloquear y generar una excepcin: "Exception in
thread "main" java.lang.StackOverflowError"
Analicemos
como
funciona:
Primero se ejecuta la funcin main, luego de crear un objeto llamamos a la funcin
repetir.
Hay que tener en cuenta que cada vez que se llama a una funcin se reservan 4 bytes
de
la
memoria
que
se
liberarn
cuando
finalice
su
ejecucin.
La primera lnea de la funcin llama a la funcin repetir, es decir que se reservan 4
bytes nuevamente. Se ejecuta nuevamente una instancia de la funcin repetir y as
sucesivamente hasta que la pila esttica se colme y se cuelgue el programa.

Problema 2:
Implementacin de un mtodo recursivo que reciba un parmetro de tipo entero y
luego llame en forma recursiva con el valor del parmetro menos 1.
Programa:

publicclassRecursividad{
voidimprimir(intx){
System.out.println(x);
imprimir(x1);
}

publicstaticvoidmain(String[]ar){
Recursividadre=newRecursividad();
re.imprimir(5);
}
}
Desde la main se llama a la funcin imprimir y se le enva el valor 5. El parmetro x
recibe el valor 5. Se ejecuta el algoritmo de la funcin, imprime el contenido del
parmetro (5) y seguidamente se llama a una funcin, en este caso a s misma (por
eso decimos que es una funcin recursiva), envindole el valor 4.
El parmetro x recibe el valor 4 y se imprime en pantalla el cuatro, llamando
nuevamente
a
la
funcin
imprimir
envindole
el
valor
3.
Si continuamos este algoritmo podremos observar que en pantalla se imprime:
5 4 3 2 1 0 ?1 ?2 ?3 . . . . . . . . .
hasta que se bloquee el programa.
Tener en cuenta que cada llamada a una funcin consume 4 bytes por la llamada y en
este caso 4 bytes por el parmetro x. Como nunca finaliza la ejecucin completa de las
funciones se desborda la pila esttica por las sucesivas llamadas.

Problema 3:

Implementar un mtodo recursivo que imprima en forma descendente de 5 a 1 de uno


en uno.
Programa:

publicclassRecursividad{
voidimprimir(intx){
if(x>0){
System.out.println(x);
imprimir(x1);
}
}

publicstaticvoidmain(String[]ar){
Recursividadre=newRecursividad();
re.imprimir(5);
}
}
Ahora si podemos ejecutar este programa y observar los resultados en pantalla. Se
imprimen los nmeros 5 4 3 2 1 y no se bloquea el programa.
Analice qu sucede cada vez que el if (x>0) se evala como falso, a qu lnea del
programa retorna?

Problema 4:
Imprimir los nmeros de 1 a 5 en pantalla utilizando recursividad.
Programa:

publicclassRecursividad{
voidimprimir(intx){
if(x>0){
imprimir(x1);
System.out.println(x);
}
}

publicstaticvoidmain(String[]ar){
Recursividadre=newRecursividad();
re.imprimir(5);
}
}
Con este ejemplo se presenta una situacin donde debe analizarse lnea a lnea la
ejecucin del programa y el porque de estos resultados.

Por qu se imprime en pantalla 1 2 3 4 5 ?


Veamos como se apilan las llamadas recursivas:
En la primera llamada desde la funcin main el parmetro x recibe el valor 5.

Cuando llamamos desde la misma funcin le enviamos el valor de x menos 1 y la


memoria queda de la siguiente forma:

Debemos entender que el parmetro x en la nueva llamada est en otra parte de la


memoria y que almacena un 4, nosotros le llamaremos x prima.
Comienza a ejecutarse la funcin, la condicin del if se vala como verdadero por lo
que entra al bloque y llama recursivamente a la funcin imprimir pasndole el valor 3 al
parmetro.

Nuevamente la condicin se vala como verdadero y llama a la funcin envindole un


2, lo mismo ocurre cuando le enva un 1 y un 0.

void imprimir(int x) {
if (x>0) {
imprimir(x-1);
System.out.println(x);
}
}
Cuando x vale 0 la condicin del if se vala como falsa y sale de la funcin imprimir.
Qu
lnea
ahora
se
ejecuta
?
Vuelve a la funcin main ? NO.
Recordemos que la ltima llamada de la funcin imprimir se haba hecho desde la
misma funcin imprimir por lo que vuelve a la lnea:
System.out.println(x);
Ahora si analicemos que valor tiene el parmetro x. Observemos la pila de llamadas
del grfico:

x cuarta tiene el valor 1. Por lo que se imprime dicho valor en pantalla.


Luego de imprimir el 1 finaliza la ejecucin de la funcin, se libera el espacio ocupado
por el parmetro x y pasa a ejecutarse la siguiente lnea donde se haba llamado la
funcin:
System.out.println(x);
Ahora
x
en
esta
instancia
de
la
funcin
tiene
el
valor
2.
As sucesivamente hasta liberar todas las llamadas recursivas.
Es importante tener en cuenta que siempre en una funcin recursiva debe haber un if
para finalizar la recursividad ( en caso contrario la funcin recursiva ser infinita y
provocar que el programa se bloquee)

Problema 5:
Otro problema tpico que se presenta para analizar la recursividad es el obtener el
factorial
de
un
nmero.
Recordar que el factorial de un nmero es el resultado que se obtiene de multiplicar
dicho nmero por el anterior y as sucesivamente hasta llegar a uno.
Ej. el factorial de 4 es 4 * 3 * 2 * 1 es decir 24.
Programa:

publicclassRecursividad{
intfactorial(intfact){
if(fact>0){
intvalor=fact*factorial(fact1);
returnvalor;
}else
return1;
}

publicstaticvoidmain(String[]ar){
Recursividadre=newRecursividad();
intf=re.factorial(4);
System.out.println("Elfactorialde4es
"+f);
}
}
La funcin factorial es recursiva porque desde la misma funcin llamamos a la funcin
factorial.

Debemos hacer el seguimiento del problema para analizar como se calcula.


La memoria en la primera llamada:

fact recibe el valor 4 y valor se cargar con el valor que se obtenga con el producto de
fact por el valor devuelto por la funcin factorial (llamada recursiva)

Nuevamente se llama recursivamente hasta que el parmetro fact reciba el valor 0.

Cuando fact recibe un cero la condicin del if se vala como falsa y ejecuta el else
retornando un 1, la variable local de la llamada anterior a la funcin queda de la
siguiente manera:

Es importantsimo entender la liberacin del espacio de las variables locales y los


parmetros
en
las
sucesivas
llamadas
recursivas.
Por ltimo la funcin main recibe "valor", en este caso el valor 24.

Problema 6:
Implementar un mtodo recursivo para ordenar los elementos de un vector.

Programa:

classRecursivdad{
staticint[]vec={312,614,88,22,54};
voidordenar(int[]v,intcant){
if(cant>1){
for(intf=0;f<cant1;f++)
if(v[f]>v[f+1]){
intaux=v[f];
v[f]=v[f+1];
v[f+1]=aux;
}
ordenar(v,cant1);
}
}
voidimprimir(){
for(intf=0;f<vec.length;f++)
System.out.print(vec[f]+"");
System.out.println("\n");
}

publicstaticvoidmain(String[]ar){
Recursivdadr=newRecursivdad();
r.imprimir();
r.ordenar(vec,vec.length);
r.imprimir();
}
}

Unidad Ill Estructuras Lineales


En la siguiente investigacin hablaremos sobre el tema de LISTAS, as
como puntos importantes de estas.
Definiremos el concepto de dicho tema, as como los tipos y, operaciones
bsicas y caractersticas de cada lista.
Trataremos sobre la aplicacin de cada una de ellas y sobre su estructura
en java.

Pilas En Java
Cuando se presentaron los arreglos, en el captulo 1, se mencion que eran estructuras
Lineales. Es decir, cada componente tiene un nico sucesor y un nico predecesor con
Excepcin del primero y del ltimo, respectivamente. Por otra parte, al analizar las operaciones
de insercin y eliminacin, se observ que los elementos se podan insertar o
Eliminar en cualquier posicin del arreglo. Cabe sealar, sin embargo, que existen problemas
Que por su naturaleza requieren que los elementos se agreguen o se quiten slo
Por un extremo. Este captulo se dedica al estudio de pilas y colas, que son estructuras
De datos lineales con restricciones en cuanto a la posicin en la cual se pueden llevar a
Cabo las operaciones de insercin y eliminacin de componentes.

Una pila representa una estructura lineal de datos en la que se puede agregar o quitar
Elementos nicamente por uno de los dos extremos. En consecuencia, los elementos de
Una pila se eliminan en orden inverso al que se insertaron; es decir, el ltimo elemento
Que se mete en la pila es el primero que se saca. Debido a esta caracterstica, se le conoce
Como estructura LIFO (Last-Input, First-Output: el ltimo en entrar es el primero
en salir).
Existen numerosos casos prcticos en los que se utiliza el concepto de pila; por
ejemplo, una pila de platos, una pila de latas en un supermercado, una pila de libros que
se exhiben en una librera, etctera. En la figura 3.1 se observa una pila de platos. Es de
suponer que si el cocinero necesita un plato limpio, tomar el que est encima de todos,
que es el ltimo que se coloc en la pila.
Las pilas son estructuras de datos lineales, como los arreglos, ya que los componentes
ocupan lugares sucesivos en la estructura y cada uno de ellos tiene un nico sucesor
y un nico predecesor, con excepcin del ltimo y del primero, respectivamente.
Una pila se define formalmente como una coleccin de datos a los cuales se puede
acceder mediante un extremo, que se conoce generalmente como tope.

Una Pila en palabras sencillas es un lugar donde se almacenan datos, al igual que en un Array, pero
una Pila tiene una filosofa de entrada y salida de datos, esta filosofa es la LIFO (Last In First Out, en
espaol, ultimo en entrar, primero en salir). Esta estructura de datos tiene muchas aplicaciones debido a
su simplicidad.
Ahora vamos a implementar esta estructura en lenguaje de programacin Java, aunque debemos tomar
en cuenta que esta clase Pila ya existe en el API de Java, con el nombre Stack (Pila en ingles) en el
paquete java.util pero de esta clase hablamos en otro Post. Ahora implementemos desde cero, que nos

ayudara a entender su funcionamiento y adems porque nos lo piden en materias de estructura de


datos. La versin que veremos esta muy bien estructurada y con tipo de datos primitivos, por eso
utilizaremos un Array para almacenar los datos asindolo as no dinmica (porque no la dimensin de
un vector la declaramos en cuando se la crea, es de dimisin fija).
Grficamente a una pila la representamos as:

Esta pila tiene 4 elementos, para la implementacin de la clase haremos uso una variable entera tope
que se encargara de decirnos en que posicin del Array esta el elemento de la cima, en este
caso tope=3 porque en el Array donde ingresamos los datos desde la posicin 0, entonces los atributos
son:
private final int MAXIMO = 100;
private int[] V;
private int tope;
El atributo MAXIMO podemos poner un numero grande considerando la cantidad aproximada que
deseemos
almacenar.
Los mtodos principales de una Pila son:

Siguiendo

la

esVacia()

retorna verdad o falso si la


Pila esta vaca, es decir que
no tiene ningn elemento,
retorna un boolean.

apilar(int a)

adiciona el elemento a en la
Pila.

desapilar()

elimina el elemento de la
cima de la pila.

vaciar(Pila B)

vaca todo el contenido de la


Pila B en la Pila, dejando a B
vaca.

tamanio()

retorna cuantos elementos


tenemos en la Pila.

cima()

retorna el elemento de la
cima sin eliminarlo de la Pila.

mostrar()

muestra todos los elementos


de la Pila en modo Consola.

filosofa

se

adicionar

elementos

apilando

uno

debajo

de

otro.

Para

eliminar

un

elemento,

se

extrae

desapila

un

elemento

por

la

cima.

Otro mtodo que necesita explicacin es el mtodo vaciar, un mtodo muy til que tambin utilizamos
para
mostrar
la
Pila
es
el vaciar.

Luego

la

Pila

principal

queda

vaca

la

pila

queda

as:

Representacin de pilas.
Las pilas no son estructuras fundamentales de datos; es decir, no estn definidas como
tales en los lenguajes de programacin. Para su representacin requieren el uso de otra
Estructuras de datos, como:
Arreglos
Listas
En este libro se utilizarn arreglos. En consecuencia, es importante definir el tamao
Mximo de la pila, as como una variable auxiliar a la que se denomina TOPE. ES! Variable
se utiliza para indicar el ltimo elemento que se insert en la pila. En la figura:
3.2 se presentan dos alternativas de representacin de una pila, utilizando arreglos.

Al utilizar arreglos para implementar pilas se tiene la limitacin de que se debe


reservar espacio de memoria con anticipacin, caracterstica propia de los arreglos. Una
vez dado un mximo de capacidad a la pila no es posible insertar un nmero de elementos
mayor al mximo establecido. Si la pila estuviera llena y se intentara insertar un
nuevo elemento, se producir un error conocido como desbordamiento -overflow-o
Por ejemplo, si en la pila que se presenta en la figura 3.30, donde TOPE = MAX, se
quisiera insertar un nuevo elemento, se producir un error de este tipo. La pila est llena
y el espacio de memoria reservado es fijo, no se puede expandir ni contraer.
Una posible solucin a este tipo de inconvenientes consiste en definir pilas de gran
tamao, pero esto ltimo resultara ineficiente y costoso si slo se utilizaran algunos
elementos. No siempre es viable saber con exactitud cul es el nmero de elementos a
tratar; por tanto, siempre existe la posibilidad de cometer un error de desbordamiento
-si se reserva menos espacio del que efectivamente se usar- o bien de hacer uso ineficiente
de la memoria -si se reserva ms espacio del que realmente se necesita-o
Existe otra alternativa de solucin a este problema. Consiste en usar espacios compartidos
de memoria para la implementacin de pilas. Supongamos que se necesitan
dos pilas, cada una de ellas con un tamao mximo de N elementos. Se definir entonces
un solo arreglo unidimensional de 2 * N elementos, en lugar de dos arreglos de N
elementos cada uno.
Como se ilustra en la figura 3.4, la PILAI ocupar desde la posicin 1 en adelante
(2,3, ... ), mientras que la PILA2 ocupar desde la posicin 2*N hacia atrs (2*N - 1.
2*N - 2, ... ). Si en algn punto del proceso la PILA 1 necesitara ms espacio del que
realmente tiene -N- y en ese momento la PILA2 no tuviera ocupados sus N lugares.
Entonces sera posible agregar elementos a la PILA 1 sin caer en un error de desbordamiento (figura
3.5). Algo similar podra suceder para la PILA2, si sta en C.eS~
N espacios y la PILA 1 tuviera lugares disponibles (figura 3.5h).

Otro error que se puede presentar al trabajar con pilas es tratar de eliminar un elemento
de una pila vaca. Este tipo de error se conoce cmo subdesbordamiento -UTr
derflow-. Por ejemplo, si en la pila que se presenta en la figura 3.3c, donde TOPE < ~
se deseara eliminar un elemento, se presentara un error de este tipo.

Operaciones con pilas.


La definicin de una estructura de datos queda completa al incluir las operaciones que pueden
Realizar en ella. Para el caso de las pilas, las operaciones bsicas que se puede:
llevar a cabo son:
Insertar un elemento -Push- en la pila
Eliminar un elemento -Pop- de la pila

Y las operaciones auxiliares:


Pila_vaca
Pila_llena
Considerando que se tiene una pila con capacidad para almacenar un nmero mximo
de elementos -MAX-, y que el ltimo de ellos se indica con TOPE, a continuacin
se presentan los algoritmos correspondientes a las operaciones mencionadas. Si la
pila est vaca, entonces TOPE es igual a O.

Aplicaciones de pilas
Las pilas son una estructura de datos muy usada en la solucin de diversos tipos de
problemas, en el rea de la computacin. Ahora se analizarn algunos de los casos ms
representativos de aplicacin de las mismas:

Llamadas a subprogramas
Recursividad
Tratamiento de expresiones aritmticas
Ordenacin

Llamadas a subprogramas
Cuando se tiene un programa que llama a un subprograma, tambin conocido como
mdulo o funcin, internamente se usan pilas para guardar el estado de las variable
del programa, as como las instrucciones pendientes de ejecucin en el momento que hace
la llamada. Cuando termina la ejecucin del subprograma, los valores almacenad
en la pila se recuperan para continuar con la ejecucin del programa en el punto en ~
cual fue interrumpido. Adems de las variables se recupera la direccin del programa
la que se hizo la llamada, porque a esa posicin se regresa el control del proceso.
Supongamos, por ejemplo, que se tiene un programa principal (PP) que llama _
los subprogramas UNO y.DOS. A su vez, el subprograma DOS llama al TRES. Caevez
que la ejecucin de uno de los subprogramas concluye, se regresa el control al ni\~
inmediato superior (fig. 3.8).
Cuando el programa PP llama a UNO, se guarda en una pila la posicin en la q
se hizo la llamada (fig. 3.9a). Al terminar UNO, el control se regresa a PP recuperan'
previamente la direccin de la pila (fig. 3.9b). Al llamar a DOS, nuevamente se guarC...
la direccin de PP en la pila (fig. 3.9c). Cuando DOS llama a TRES, se pone en la pila
direccin de DOS (fig. 3.9d). Despus de procesar TRES, se recupera la posicin'
DOS para continuar con su ejecucin (fig. 3.ge). Al terminar DOS se regresa el con
a PP, obteniendo previamente la direccin guardada en la pila (fig. 3.9j).
Finalmente podemos concluir que las pilas son necesarias en este tipo de aplicacicnes
por lo siguiente:

Permiten guardar la direccin del programa, o subprograma, desde donde se hizo la


llamada a otros subprogramas, para regresar posteriormente y seguir ejecutndolo a
partir de la instruccin inmediata a la llamada.
Permiten guardar el estado de las variables en el momento en que se hace la llamada,
para seguir ocupndolas al regresar del subprograma.
Tratamiento de expresiones aritmticas.
Un problema interesante en computacin consiste en convertir expresiones en notacin
infija a su equivalente en notacin prefija o posfija o prefija. Se presenta primero una
breve introduccin a estos conceptos.
Dada la expresin A + B se dice que sta se encuentra en notacin infija
operador C +) se encuentra entre los operandos CA y B).
Dada la expresin AB+ se dice que sta se encuentra en notacin postfija, po
el operador (+) se encuentra despus de los operandos (A y B).
Dada la expresin +AB se dice que sta se encuentra en notacin prefija, porque=
operador (+) est precediendo a los operandos (A y B).
La ventaja de usar expresiones en notacin postfija o prefija radica en que no
necesarios los parntesis para indicar orden de operacin, ya que ste queda establec::.
por la ubicacin de los operadores con respecto a los operandos.
Para convertir una expresin dada en notacin infija a una en notacin postfij~
prefija se establecen primero ciertas condiciones:
Solamente se manejarn los siguientes operadores -se presentan de mayor a Gl

segn sea su prioridad de ejecucin;


Los operadores de ms alta prioridad se ejecutan primero.
Si hubiera en una expresin dos o ms operadores de igual prioridad, entonces
procesarn de izquierda a derecha.
Las subexpresiones que se encuentran entre parntesis tendrn ms prioridad
cualquier operador.

La clase Pila
La clase Pila tiene atributos y mtodos. Los atributos son la coleccin de element~
el TOPE. Los mtodos, por otra parte, son todas aquellas operaciones analizadas e

seccin anterior -Pila_vaca, Pila_llena, Pone y Quita-. En la figura 3.10 se pu


observar grficamente la clase Pila.
Se tiene acceso a los miembros de un objeto de la clase Pila por medio de la n _
cin de puntos. Al asumir que la variable PIOBJ representa un objeto de la clase
previamente creado, se puede hacer:
PIOBJ.Pila_llena para invocar el mtodo que determina si la pila est llena o
En este mtodo no hay argumentos, ya que todos los valores requeridos son miembro de la clase.
PIOBJ.Pone(argumento) para insertar un nuevo elemento en la pila. En este mtoo..
slo hay un argumento que indica el valor a guardar en la pila; los dems valores requeridos son
miembros de la clase.

Colas En Java
Una cola es simplemente un lugar para almacenar cosas, donde esas cosas se insertan una detrs de
otra y para extraer siempre se lo hace por adelante de la cola donde se encuentra el primer elemento.
Una cola funciona como una fila o cola de personas, que esperan su turno para ser atendidas, la
primera persona atendida es siempre la primera de la fila y cuando llega una persona y queremos
incorporarla a cola o adicionarla debemos hacerlo por detrs de la ultima persona en la cola.

Con fines educativos una cola se la puede representar grficamente as:

Una cola puede almacenar lo que nosotros queramos, nmeros, personas, documentos, cualquier cosa.
Esta estructura de datos tiene muchas aplicaciones en la informtica al igual que la pila, por ejemplo
cuando mandan a imprimir varios documentos a una impresora, existe una cola de impresin que sigue
la filosofa, se imprimen los primeros documentos y si quiero imprimir un nuevo documento se adiciona
al final de todos los documentos que estn esperando a imprimirse.
Una vez comprendido en concepto ahora veamos como se implementa esto en un lenguaje de
programacin, por ahora lo implementaremos en Java. Java en sus libreras ya tiene la forma de
implementar Colas (queue), nosotros ahora haremos como si no existiera, es decir crearemos nuestra
versin, que es lo que generalmente se hace cuando se aprende colas en la universidad. Pues bien
existen dos formas de implementar para que la cola sea o bien esttica (reservamos un espacio fijo en
memoria) o bien dinmica (el tamao en memoria va creciendo segn se requiere), se implementa con
arrays o con listas enlazadas respectivamente. Nosotros implementaremos haciendo de un array
bidimensional es decir de modo esttico y al decir esttico estamos diciendo que tendr un limite para
almacenar datos en la cola.
Para manipular elementos en el vector de la cola son necesarias variables que me digan en donde
empiezan los elementos y otra en donde terminan, como tal vez ests pensando porque no solo una?
(una que me diga en donde termina asumiendo que siempre se empieza desde la posicin 0 del array),
pues se puede implementar con solo una de estas variables pero presenta muchas desventajas pues si
eliminamos un elemento de nuestra cola, (el primero justamente) tendramos que recorrer todos los
siguientes elementos una posicin adelante y esta manera seria muy lenta de implementar pues que
pasa si son 1000 elementos, eso es mucho tiempo perdido, entonces es por eso que usamos dos
variables que me digan donde empieza y donde terminan los elementos de la cola, dos variables
enteras que llamaremos inicio y fin, estas variables funcionan de la siguiente manera:

Consideremos que nuestro array bidimensional o vector lo creamos con 10 posiciones enumeradas del
0 al 9, la variable inicio guarda una posicin antes en la cual se encuentra el primer elemento y la
variable fin guarda la posicin en donde se encuentra justamente el ultimo elemento.
Entonces los atributos que tendr nuestra clase Cola de nmeros enteros sern:
private final int MAXIMO = 101;
private int[] V;
private int inicio;
private int fin;
Ahora una vez teniendo esta estructura hay que definir los mtodos principales para manejar una cola,
estos mtodos son:

esVacia() : boolean

retorna verdad si la cola


esta vaca es decir no tiene
ningn elemento, para esto
solo se pregunta
si inicio es igual afin.

esLlena() : boolean

retorna verdad si es que la


cola esta llena, pasa
cuando se ha llenado todo
el vector, la cantidad de
elemento que permite la
cola lo determina la

variable MAXIMO.
adicionar(int a)

adiciona un nuevo
elemento a la cola, para
esto solo se incrementa la
variable fin y se coloca el
elemento en esa posicin.

eliminar() : int

extrae el primer elemento


de la cola, para esto se
retorna la posicin inicio +
1 del vector y se
incrementa inicio en 1.

tamanio() : int

retorna la cantidad de
elementos que tiene la
cola, para realizar esto se
realiza la resta fin - inicio.

copiar(Cola B)

copia tal cual la cola B a la


cola destino, al finalizar
cola B queda totalmente
vaca. Este mtodo es muy
til al momento de hacer
operaciones con colas.

Con todos estos mtodos bsicos se puede realizar cualquier operacin que necesitemos, ahora
puedes descargarte la clase Cola de nmeros enteros y otra clase cola para objetos Persona, tu puedes
crear tu propia clase Cola que almacene lo que quieras ya solo hay que cambiar cierta parte del cdigo.

Nota: Se suele implementar a veces una cola llamada cola circular, en realidad no existe una cola
circular pues es la misma que se implementa en todas partes suelen llamarla as porque su
implementacin simula que los elementos van rotando en nuestro vector o array, sin embargo da lo
mismo que cualquier cola, es por eso que solo existe o debera existir una clase Cola.

Una cola constituye una estructura lineal de datos en la que los nuevos elementos se introducen por un
extremo y los ya existentes se eliminan por el otro. Es importante sealar que los componentes de la
cola se eliminan en el mismo orden en el cual se insertaron. Es decir, el primer elemento que se
introduce en la estructura ser el que se eliminar en primer orden. Debido a esta caracterstica, las
colas tambin reciben el nombre de estructuras FIF (First-In, First-Out: el primero en entrar es el
primero ensalir).
Existen numerosos casos de la vida real en los cuales se usa este concepto. Ejemplo, la cola de los
bancos en las que los clientes esperan para ser atendidos -;..
primera persona de la cola ser la primera en recibir el servicio-, la cola de los nio.
que esperan a veces pacientemente para subir a un juego mecnico, las colas de 1 vehculosesperando
la luz verde del semforo, las colas para entrar a un cine, teatro'
estadio de ftbol, etctera.
Especificaciones del tipo abstracto de datos Cola
Las operaciones que sirven para definir una cola y poder manipular su contenido son las siguientes:

Tipo de dato Elemento que se almacena en la cola.

Operaciones
Crear Cola
Insertar
Quitar
Cola vaca
Cola llena

Inicia la cola como vaca.


Aade un elemento por el final de la cola.
Retira (extrae) el elemento frente de la cola.
Comprueba si la cola no tiene elementos.
Comprueba si la cola est llena de elementos.

Tamao de la cola Nmero de elementos mximo que puede contener la cola. En una cola, al igual que
en una pila, los datos se almacenan de un modo lineal y el acceso a los datos slo est permitido en los
extremos de la cola. La forma que los lenguajes tienen para representar el TAD Cola depende de donde
se almacnenlos elementos: en un arrays, en una estructura dinmica como puede ser un Vector
(contenedor de Java) o en una lista enlazada. La utilizacin de arrays tiene el problema de que la cola
no puede crecer indefinidamente, est limitada por el tamao del array; como contrapartida, el acceso a
los extremos es muy eficiente. Utilizar una lista enlazada permite que el nmero de nodos se ajuste al
de elementos de la cola; por el contrario, cada nodo necesita memoria extra para el enlace, y tambin
hay que tener en cuenta el lmite de memoria de la pila del computador

Representacin de colas
Las colas, al igual que las pilas, no existen como estructuras de datos
estndar en los lenguajes de programacin. Este tipo de estructura de
datos se puede representar filOdiante el uso de:
Arreglos
Listas'
Al igual que en el caso de las pilas, en este libro se utilizarn arreglos para
fi05trar su funcionamiento. Sin embargo, la implementacin mediante
listas es incluso sencilla. El lector puede implementar los algoritmos
necesarios para colas, despus ci: estudiar el captulo que se dedica a la
estructura lineal de datos.Cuando se implementan con arreglos
unidimensionales, es importante definirtamao mximo para la cola y dos
variables auxiliares. Una de ellas para que al cene la posicin del primer
elemento de la cola -FRENTE- y otra para que guaro: la posicin del
ltimo elemento de la cola -FINAL-. En la figura 3.12 se muestra en la
representacin de una cola en la cual se han insertado tres elementos:
111, 222 _ 333, en ese orden. El elemento 111 est en el FRENTE ya que
fue el primero que se insert el elemento 333, que fue el ltimo en entrar,
est en el FINAL de la cola.

Operaciones con colas


La definicin de la estructura de datos tipo cola queda completa al incluir las operacionesque se pueden
realizar en ella. Las operaciones bsicas que pueden efectuarse son:
Insertar un elemento en la cola
Eliminar un elemento de la cola
Las inserciones se llevarn a cabo por el FINAL de la cola, mientras que las eliminaciones se harn por
el FRENTE -recuerde que el primero en entrar es el primero en salir-, Considerando que una cola puede
almacenar un mximo nmero de elementos y que adems FRENTE indica la posicin del primer
elemento y FINAL la posicin del ltimo, se presentan a continuacin los algoritmos correspondientes a
las operaciones mencionadas. A1ron''110 ~ Inserta_cola (COLA, MA ,FREl TE, FI AL, DATO) {Este
algoritmo inserta el elemento DATO al final de una estructura tipo cola. fREI.'TE ~. FINAL son los
punteros que indican, respectivamente, el inicio y fin de COLA. La pri.meIa vez FRENTE y FINAL tienen
el valor O, ya que la cola est vaca. MAX es el mximo nm'lDelro (k elementos que puede almacenar
la cola)

PILAS y COLAS
1. Si (FINAL < MAX) {Verifica que hay espacio libre}
entonces
Hacer FINAL +- FINAL + 1 {Actualiza FINAL} y COLA[FINAL] +- DATO
1.1 Si (FINAL = 1) entonces {Se insert el primer elemento de COLA}
Hacer FRENTE +- 1
1.2 {Fin del condicional del paso 1.1}
si no
Escribir "Desbordamiento - Cola llena"
2. {Fin del condicional del paso l}

AlgOlitmo 3.8 Elimina301a


Elimina_cola (COLA, FRENTE, FINAL, DATO)
{Este algoritmo elimina el primer elemento de una estructura tipo cola y lo almacena en DATO. FRENTE
YFINAL son los punteros que indican, respectivamente, el inicio y fin de la cola}
1. Si (FRENTE,. O) {Verifica que la cola no est vaca}
entonces
Hacer DATO +- COLA [FRENTE]
1.1 Si (FRENTE = FINAL) {Si hay un solo elemento}
entonces
Hacer FRENTE +- O y FINAL +- O{Indica COLA vaca}
si no
Hacer FRENTE +- FRENTE + 1
1.2 {Fin del condicional del paso l.l}
si no
Escribir "Subdesbordamiento - Cola vaca" 2. {Fin del condicional del paso l} Es posible definir
algoritmos auxiliares para determinar si una cola est llena . A partir de estos algoritmos se podran
reescribir los algoritmos 3.7 y 3.8. A continuacin se presentan los algoritmos que permiten verificar el
estado de cola, quedando como tarea sugerida la reescritura de los dos algoritmos anteriores. Algoritmo.

Mtodo Cola vaca


Cola_vaca (COLA, FRENTE, BAND) {Este algoritmo determina si una estructura de datos tipo cola est
vaca, asignando a B~ el valor de verdad correspondiente}
1. Si (FRENTE =O)
entonces
Hacer BAND +- VERDADERO
si no
Hacer BAND +- FALSO
2 {Fin del condicional del paso l}

Mtodo Cola llena


Cola_llena (COLA, FINAL, l\lAX, BAND)
{Este algoritmo determina si una estructura de datos tipo cola est llena, asignando a BAND
el valor de verdad correspondiente. MAX es el nmero mximo de elementos que puede
almacenar COLA}
. Si (FINAL = MAX)
entonces
Hacer BAND +- VERDADERO
si no
Hacer BAND +- FALSO
{Fin del condicional del paso l}
emplo 3.6
Aqu se incluye un ejemplo para ilustrar el ful). cionarniento de las operaciones de insercin y
eliminacin en colas. Retome el ejemplo
3.1 de la seccin
3.1.2. Se insertan en COLA los elementos: lunes, martes, mircoles, jueves y viernes -en ese orden-, de
modo que la estructura queda como se muestra en la figura
3.14. Para este ejemplo MAX =7.

COLA CON UN ARRAY CIRCULAR


La alternativa, sugerida en la operacin de quitar un elemento, de desplazar los restantes elementos del
array de modo que la cabeza de la cola vuelva al principio del array, es costosa en trminos de tiempo
de computadora, especialmente si los datos almacenados en el array son
estructuras de datos grandes. La forma ms eficiente de almacenar una cola en un array es modelarlo
de tal forma que se una el extremo final con el extremo cabeza. Tal array se denomina array circular y
permite que la totalidad de sus posiciones se utilicen para almacenar elementos de la cola sin necesidad
de desplazar elementos. La Figura 10.4 muestra un array circular de n elementos.

El array se almacena de modo natural en la memoria como un bloque lineal de n elementos. Se


necesitan dos marcadores (apuntadores) frente y fin para indicar, respectivamente, la posicin del
elemento cabeza y del ltimo elemento puesto en la cola.

El frente siempre contiene la posicin del primer elemento de la cola y avanza en el sentido de las
agujas del reloj; fin contiene la posicin donde se puso el ltimo elemento y tambin avanza en el
sentido del reloj (circularmente a la derecha). La implementacin del movimiento circular se
realiza segn la teora de los restos, de tal forma que se generen ndices de 0 a MAXTAMQ-1:

Clase Cola con array circular


La clase declara los apuntadores frente, fin y el array listaCola[]. Para obtener la siguiente posicin de
una dada aplicando la teora de los restos, se escribe el mtodo siguiente(). A continuacin, se codifican
los mtodos que implementan las operaciones del TAD Cola. Ahora el tipo de los elementos es Object,
de tal forma que se pueda guardar cualquier tipo de
elementos.
package TipoCola;
public class ColaCircular
{
private static fin int MAXTAMQ = 99;
protected int frente;
protected int fin;
protected Object [] listaCola ;
// avanza una posicin
private int siguiente(int r)
{
return (r+1) % MAXTAMQ;
}
//inicializa la cola vaca
public ColaCircular()
{

frente = 0;
fin = MAXTAMQ-1;
listaCola = new Object [MAXTAMQ];
}
// operaciones de modificacin de la cola
public void insertar(Object elemento) throws Exception
{
if (!colaLlena())
{
fin = siguiente(fin);
listaCola[fin] = elemento;
}
else
throw new Exception("Overflow en la cola");
}
public Object quitar() throws Exception
{
if (!colaVacia())
{
Object tm = listaCola[frente];
frente = siguiente(frente);
return tm;
}
else
throw new Exception("Cola vacia ");
}
public void borrarCola()
{
frente = 0;
fin = MAXTAMQ-1;
}
// acceso a la cola
public Object frenteCola() throws Exception
{
if (!colaVacia())
{
return listaCola[frente];
}
else
throw new Exception("Cola vacia ");
}
// mtodos de verificacin del estado de la cola
public boolean colaVacia()
{
return frente == siguiente(fin);
}
// comprueba si est llena
public boolean colaLlena()
{
return frente == siguiente(siguiente(fin));

Encontrar un numero capica ledo del dispositivo estndar de


entrada.
El algoritmo para encontrar un nmero capica utiliza conjuntamente una Cola y una Pila. El nmero se
lee del teclado, en forma de cadena con dgitos. La cadena se procesa carcter a carcter, es decir,
dgito a dgito (un dgito es un carcter del 0 al 9). Cada dgito se pone en una
cola y, a la vez, en una pila. Una vez que se terminan de leer los dgitos y de ponerlos en la Cola y en la
Pila, comienza el paso de comprobacin: se extraen en paralelo elementos de la cola y de la pila, y se
comparan por igualdad. De producirse alguna no coincidencia entre dgitos, significaEncontrar un
numero capica ledo del dispositivo estndar de entrada. El algoritmo para encontrar un nmero
capica utiliza conjuntamente una Cola y una Pila. El nmero se lee del teclado, en forma de cadena
con dgitos. La cadena se procesa carcter a carcter, es decir, dgito a dgito (un dgito es un carcter
del 0 al 9). Cada dgito se pone en una cola y, a la vez, en una pila. Una vez que se terminan de leer los

dgitos y de ponerlos en la Cola y en la Pila, comienza el paso de comprobacin: se extraen en paralelo


elementos de la cola y de la pila, y se comparan por igualdad. De producirse alguna no coincidencia
entre dgitos, significa
import TipoPila.PilaVector;
import TipoCola.ColaCircular;
import java.io.*;
public class Capicua
{
public static void main(String [] a)
{
boolean capicua;
BufferedReader entrada = new BufferedReader(
new InputStreamReader(System.in));
String numero;
PilaVector pila = new PilaVector();
ColaCircular q = new ColaCircular();
try {
capicua = false;
while (!capicua)
{
do {
System.out.print("\nTeclea el nmero: ");
numero = entrada.readLine();
}while (! valido(numero)); // todos dgitos
// pone en la cola y en la pila cada dgito
for (int i = 0; i < numero.length(); i++)
{
Character c;
c = new Character(numero.charAt(i));
q.insertar(c);
pila.insertar(c);
}
// se retira de la cola y la pila para comparar
do {
Character d;
d = (Character) q.quitar();
capicua = d.equals(pila.quitar());//compara por igualdad
} while (capicua && !q.colaVacia());
if (capicua)
System.out.println(numero + " es capica. ");
else
{
System.out.print(numero + " no es capica, ");
System.out.println(" intente otro. ");
// se vaca la cola y la pila
q.borrarCola();
pila.limpiarPila();
}
}
}
catch (Exception er)
{
System.err.println("Error (excepcion) en el proceso: " + er);
}
}
private static boolean valido(String numero)
{
boolean sw = true;
int i = 0;
while (sw && (i < numero.length()))
{
char c;
c = numero.charAt(i++);

sw = (c >= '0' && c <= '9');


}
return sw;
}
}

COLA CON UNA LIST A ENLAZADA


La implementacin del TAD Cola con un array necesita reservar memoria para el mximo de elementos
previstos. En muchas ocasiones, esto da lugar a que se desaproveche memoria, pero tambin puede
ocurrir los contrario, que se llene la cola y no se pueda seguir con la ejecucin del
programa a no ser que se ample la capacidad del array. Un diseo alternativo consiste en utilizar una
lista enlazada que, en todo momento, tiene el mismo nmero de nodos que elementos la cola. La
implementacin del TAD Cola con una lista enlazada permite ajustarse exactamente al nmero de
elementos de la cola. Utiliza dos apuntadores (referencias) para acceder a la lista, frente y fin, que son
los extremos por donde salen y por donde se ponen, respectivamente, los elementos de la cola.

BICOLAS: COLAS DE DOBLE ENTR ADA


La estructura bicola o cola de doble entrada se puede considerar que es una extensin del TAD Cola.
Una bicola es un conjunto ordenado de elementos, al que se puede aadir o quitar elementos desde
cualquier extremo del mismo. El acceso a la bicola est permitido desde cualquier extremo. Se puede
afirmar que una bicola es una cola bidireccional . Los dos extremos de una bicola se pueden identificar
como frente y fin (iguales nombres que en una cola). Las operaciones bsicas que definen una bicola
son una ampliacin de la operaciones que definen una cola:

Crear Bicola Inicializa una bicola sin elementos.


Bicola Vacia Devuelve true si la bicola no tiene elementos.
Poner Frente Aade un elemento por el extremo frente.
Poner Final Aade un elemento por el extremo final.
Quitar Frente Devuelve el elemento Frente y lo retira de la bicola.
Quitar Final Devuelve el elemento Final y lo retira de la bicola.
Frente
Devuelve el elemento que se encuentra en el extremo frente.
Final
Devuelve el elemento que se encuentra en el extremo final.
Al tipo de datos bicola se le puede poner restricciones respecto a la entrada o a la salida de elementos.
Una bicola con restriccin de entrada es aquella que slo permite inserciones por uno de los dos
extremos, pero que permite retirar elementos por los dos extremos. Una bicola con restriccin de salida
es aquella que permite inserciones por los dos extremos, pero slo permite retirar elementos por uno de
ellos. La representacin de una bicola puede ser con un array, con un array circular, con una coleccin
tipo Vector o bien con listas enlazadas. Siempre se debe disponer de dos marcadores o variables ndice
(apuntadores) que se correspondan con ambos extremos, frente y final, respectivamente,de la
estructura.
Bicola con listas enlazadas
La implementacin del TAD Bicola con una lista enlazada se caracteriza por ajustar el tamao al nmero
de elementos; es una implementacin dinmica, crece o decrece segn lo requiera laejecucin del
programa que utiliza la bicola. Para esta representacin de la bicola es necesario declarar
la clase Nodo con los atributos elemento y siguiente (enlace con el siguiente nodo), y la clase Bicola con
los atributos frente y fin y los mtodos que implementan las operaciones. Se observa que las
operaciones del TAD Bicola son una extensin de las operaciones del TAD
Cola, incluso son idnticas las variables de acceso: frente y fin. Por esta razn, resulta ms eficiente
implementar la clase Bicola con herencia de la clase ColaLista: public class Bicola extends ColaLista
De esta forma, Bicola dispone de todos los mtodos y atributos de la clase ColaLista. Entonces, slo es
necesario codificar las operaciones de Bicola que no estn implementadas en ColaLista. Los mtodos

ponerFinal(), quitarFrente(), bicolaVacia(), frenteBicola() se corresponden con insertar(), quitar(),


colaVacia() y frenteCola() de la clase ColaLista. Se aade el mtodo numElemsBicola() para contar los
elementos que estn guardados. La implementacin completa es la siguiente:
package TipoCola;
public class Bicola extends ColaLista
{
// inicializa frente y fin a null
public Bicola()
{
super();
}
// inserta por el final de la Bicola
public void ponerFinal(Object elemento)
{
insertar(elemento); // mtodo heredado de ColaLista
}
// inserta por el frente; mtodo propio de Bicola
public void ponerFrente(Object elemento)
{
Nodo a;
a = new Nodo(elemento);
if (colaVacia())
{
fin = a;
}
a.siguiente = frente;
frente = a;
}
// retira elemento frente de la Bicola
public Object quitarFrente() throws Exception
{
return quitar(); // mtodo heredado de ColaLista
}
// retira elemento final; mtodo propio de Bicola.
// Es necesario recorrer la bicola para situarse en
// el nodo anterior al final, para despus enlazar.
public Object quitarFinal() throws Exception
{
Object aux;
if (!colaVacia())
{
if (frente == fin) // la Bicola dispone de un solo nodo
aux = quitar();
else
{
Nodo a = frente;
while (a.siguiente != fin)
a = a.siguiente;
// siguiente del nodo anterior se pone a null
a.siguiente = null;
aux = fin.elemento;
fin = a;
}
}
else
throw new Exception("Eliminar de una bicola vaca");
return aux;
}
public Object frenteBicola()throws Exception
{
return frenteCola(); // mtodo heredado de ColaLista
}
// devuelve el elemento final
public Object finalBicola() throws Exception
{

if (colaVacia())
{
throw new Exception("Error: cola vaca");
}
return (fin.elemento);
}
// comprueba el estado de la bicola
public boolean bicolaVacia()
{
return colaVacia(); // mtodo heredado de ColaLista
}
//elimina la bicola
public void borrarBicola()
{
borrarCola(); // mtodo heredado de ColaLista
}
public int numElemsBicola() // cuenta los elementos de la bicola
{
int n;
Nodo a = frente;
if (bicolaVacia())
n=0
Estructuras de datos en Java
{
n = 1;
while (a != fin)
{
n++;
a = a.siguiente;
}
}
return n;
}
}

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

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.

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

Arias Gonzlez, Felipe

Garca Sacedn, Manuel

Lpez Medina, Margarita

Ramrez Heredia, Jorge

Yuste Pelez, Juan

aa12
53
ax007
4
mj772
6
lp15
23
gb13
05

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
0
3

7
7
4

1
2
7

2
66

2
1
0

1
1
8

1
3
5

1
08

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


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

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
1
identificar el final de la lista .
La figura 3.5 muestra un ejemplo de implementacin dinmica de la lista de la figura
3.4.

Memoria esttica

Lista

10

Memoria dinmica

2
1
12
1
3

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.

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
Varia
ble
estti

lista1
nombr
e

inici
o

Clase Lista

1
0

d
si
at
g
o
NodoList
a

1
3

da
to

si
g

NodoList
a

Figura 3.6. Ejemplo de lista enlazada.

2
1

da
to

n
ull
si
g

NodoLista

ALGORITMOS BSICOS CON LISTAS .


Los algoritmos que implican el recorrido, parcial o total, de la lista pueden
implementarse tanto de forma recursiva como iterativa.
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.

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.

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

cima

4
1
7
2

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:

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

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

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

desencolar

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:

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
3
vaca), encolar (opera al final de la lista) , numElemCola e invertirCola.

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

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

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.

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

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.

inicio

aux
inicio
inicio

Situacin inicial
1
0
Creacin del nuevo nodo
a
ux
Insercin al principio
11
1
Situacin final
11

1
3

2
1

Null

null

10

13

11

21

nu

Figura 3.11. Insercin al principio de la lista.


4

El siguiente algoritmo muestra el mtodo completo :


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.

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


5
(nodoLista == null) .

inicio

Situacin inicial
1
0

13

21

null

Insercin al final
11

aux
inicio

inicio

1
0
Situacin final
10

13

21

13

21

n
u

1
1

nu
ll

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.

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
6
de memoria de los nodos eliminados para posibles usos posteriores.
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

Situacin final
inicio

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.

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.

ini
cio

Situacin inicial
1
0

ini
cio

Despus de buscarYSaltar
1
0

15

13

21

1
4

n
u

15

13

21

1
4

n
u

10

15

21

14

n
u

aux

Situacin final
ini
cio

13

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.

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 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.
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);
}

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.
Situacin inicial
Inicio

1
0
Creacin del nuevo nodo
a
ux

Inicio

1
0
Insercin en su lugar
a
ux

Inicio

Inicio

1
0
Situacin final
10

1
3

2
1

null

2
1

null

11
1
3
11
1
3

2
1

11

13

21

nu
ll

Figura 3.15. Insercin en una lista ordenada.


7

A continuacin se muestra un algoritmo de insercin .


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.

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)

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

Situacin final
inicio

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

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).
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);
}

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:

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

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.
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
8
ejemplo, para la implementacin de colas 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.

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

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;
}
elseSystem.out.println ("No existe el nodo de clave " + x);
}
elseSystem.out.println ("La lista est vaca ");

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
2

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
9
eliminacin (eliminar) para una lista calificada ordenada implementada mediante
estructuras dinmicas bidireccionales.

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

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
2

null

Aux

inicio

null
2

null

Nuevo

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;

null

inicio

null

aux

inicio

null

77

aux

null
77

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

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

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).
inicio

null
2

null

actual

inicio

null
2

null

Figura 3.21. Eliminacin de un nodo intermedio

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

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

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

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;

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.

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

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

IMPLEMENTACIN DE LISTAS UTILIZANDO MATRICES


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

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

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.

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

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

1
0
2
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

1
2
numNodos = 7

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

1
2
numNodos = 7

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

1
2
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

1
2
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.

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

Lista enlazada sobre matriz


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

clave
sig

1
2 30

7
47

1
72

2
66

2
1
0

1
1
8

1
3
5

1
08

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.

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.

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

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

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

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

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

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) { inti = 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;
}

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

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

S-ar putea să vă placă și