Sie sind auf Seite 1von 49

Instituto Tecnológico De Soledad Atlántico ITSA (958-57393) ISBN 978-958-59173-9-2

Manual de EStructura de Datos

1
EVELIO ARRIETA
Evelio Ramiro Arrieta Torres
MANUAL DE ESTRUCTURA DE DATOS
CONTENIDO Introducción.......................................................................................................... 4
1. COLAS............................................................................................................... 5
1.1 Definición................................................................................................... 5
1.2 Operaciones básicas con colas................................................................... 6
1.2.1 Agregar un elemento......................................................................... 6
1.2.2 Eliminar un elemento de una cola..................................................... 7
1.3 Aplicaciones del uso de Colas.................................................................. 10
1.4 Cola circular............................................................................................. 10
1.5 Ejercicios Colas........................................................................................ 12
2. PILAS............................................................................................................... 13
2.1 Definición................................................................................................. 13
2.2 Operaciones básicas con pilas................................................................. 13
2.2.1 Inserción de elementos................................................................... 14
2.2.1.1 Inserción en una pila vacía...................................................... 14
2.2.2 Eliminación en una Pila.................................................................... 15
2.5 Aplicaciones del uso de Pilas................................................................... 17
2.6 Ejercicios con Pilas................................................................................... 17
3. LISTAS ENLAZADAS.......................................................................................... 18
3.1 Definición................................................................................................. 18
3.2 Declaraciones de tipos para manejar listas............................................. 18
3.3 Operaciones básicas con listas................................................................. 19
3.3.1 Insertar elementos en una lista abierta........................................... 19
3.3.2 Eliminar elementos en una lista abierta.......................................... 19
3.4 LISTAS DOBLEMENTE ENLADAS (Listas dobles)........................................ 24
3.4.1 Operaciones básicas con listas doblemente enlazadas................... 24
3.5 Ejercicios con Listas enlazadas................................................................. 28
4. Recursividad.................................................................................................... 28
4.1 Concepto de recursividad........................................................................ 28
4.2 Ejemplos.................................................................................................. 29
4.3 Uso de la recursividad.............................................................................. 32
4.4 Ejercicios.................................................................................................. 34
5. Arboles............................................................................................................ 35
5.1 Definición................................................................................................. 35
5.2 Operaciones básicas con árboles............................................................. 36
5.3 Recorridos por árboles............................................................................ 37
CONTENIDO
5.3.1 Pre-orden................................................................................................... 37
5.3.2 In-orden..................................................................................................... 38
5.3.3 Post-orden................................................................................................. 38
5.4 Eliminar nodos en un árbol........................................................................... 38
5.5 Arboles ordenados........................................................................................ 39
5.6 Árboles binarios de búsqueda (ABB)............................................................ 40
5.6.1 Definición................................................................................................... 40
5.6.2 Operaciones en ABB.................................................................................. 40
5.6.3 Buscar un elemento................................................................................... 41
5.6.4 Insertar un elemento................................................................................. 41
5.6.5 Borrar un elemento................................................................................... 42
Ejemplo 1: Borrar un nodo hoja............................................................................... 43
Ejemplo 2: Borrar un nodo rama con intercambio de un nodo hoja.............................. 43
Ejemplo 3: Borrar un nodo rama con intercambio de un nodo rama............................ 44
5.7 EJERCICIOS.................................................................................................... 48
6. Bibliografía de Referencia............................................................................... 49
Introducción
En la práctica, la mayor parte de información útil no aparece aislada en forma de datos simples, sino que
lo hace de forma organizada y estructurada. Los diccionarios, guías, enciclopedias, etc., son colecciones
de datos que serían inútiles si no estuvieran organizadas de acuerdo con unas determinadas reglas.
Además, tener estructurada la información supone ventajas adicionales, al facilitar el acceso y el manejo
de los datos. Por ello parece razonable desarrollar la idea de la agrupación de datos, que tengan un
cierto tipo de estructura y organización interna.

La selección de una estructura de datos frente a otra, a la hora de programar es una decisión importante,
ya que ello influye decisivamente en el algoritmo que vaya a usarse para resolver un determinado
problema. El objetivo de este manual no es sólo la descripción de las distintas estructuras, sino también
la comparación de las mismas en términos de utilidad para la programación.

Empecemos recordando que un dato de tipo simple, no está compuesto de otras estructuras, que no
sean los bits, y que por tanto su representación sobre el ordenador es directa, sin embargo existen
unas operaciones propias de cada tipo, que en cierta manera los caracterizan. Una estructura de datos
es, a grandes rasgos, una colección de datos (normalmente de tipo simple) que se caracterizan por
su organización y las operaciones que se definen en ellos. Por tanto, una estructura de datos vendrá
caracterizada tanto por unas ciertas relaciones entre los datos que la constituyen (ej. el orden de las
componentes de un vector de números reales), como por las operaciones posibles en ella. Esto supone
que podamos expresar formalmente, mediante un conjunto de reglas, las relaciones y operaciones
posibles.

4
1. COLAS
1.1 Definición
Una cola es un tipo especial de lista abierta en la que sólo se puede insertar nodos en uno de los
extremos de la lista y sólo se pueden eliminar nodos en el otro. Además, como sucede con las pilas, las
escrituras de datos siempre son inserciones de nodos, y las lecturas siempre eliminan el nodo leído.

Este tipo de lista es conocido como lista FIFO (First In First Out), el primero en entrar es el primero en
salir.

El símil cotidiano es una cola para comprar, por ejemplo, las entradas del cine. Los nuevos compradores
sólo pueden colocarse al final de la cola, y sólo el primero de la cola puede comprar la entrada.

Las colas se representan por listas enlazadas (a) o por arrays (b). Se necesitan dos punteros o variables:
frente y final, y la lista o array de n elementos. 

a)
frente 100 264 119 48 final

b)

frente final

Fuente: Propia

El nodo típico para construir colas es el mismo para la construcción de listas:


struct nodo {
int dato;
struct nodo *siguiente;
}

Una cola, entonces, es una lista abierta. Así que sigue siendo muy importante que el programa nunca
pierda el valor del puntero al primer elemento, igual que pasa con las listas abiertas. Además, debido al
funcionamiento de las colas, también deberemos mantener un puntero para el último elemento de la
cola, que será el punto donde insertemos nuevos nodos.
Teniendo en cuenta que las lecturas y escrituras en una cola se hacen siempre en extremos distintos, lo
más fácil será insertar nodos por el final, a continuación del nodo que no tiene nodo siguiente, y leerlos
desde el principio, hay que recordar que leer un nodo implica eliminarlo de la cola.

5
1.2 Operaciones básicas con colas
De nuevo nos encontramos ante una estructura con muy pocas operaciones disponibles. Las colas sólo
permiten agregar y leer elementos:

· Agregar: Inserta un elemento al final de la cola.

· Leer: Lee y elimina un elemento del principio de la cola.

1.2.1 Agregar un elemento


Las operaciones con colas son muy sencillas, prácticamente no hay casos especiales, salvo que la cola
esté vacía.

1.2.1.1 Agregar elemento en una cola vacía

Cola vacía
Partiremos de que ya tenemos el nodo a insertar y, por supuesto un puntero que apunte a él, además
los punteros que definen la cola, primero y último que valdrán NULO:

Fuente: Propia

Elemento encolado
El proceso es muy simple, bastará con que:

1. Hacer que nodo.siguiente apunte a NULO.

2. Que el puntero primero apunte a nodo.

3. Y que el puntero último también apunte a nodo.

1.2.1.2 Agregar elemento en una cola no vacía

Fuente: Propia

6
Cola no vacía
De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una cola, en este caso,
al no estar vacía, los punteros primero y último no serán nulos:
Elemento insertado en la cola
El proceso sigue siendo muy sencillo:

1. Hacemos que nodo.siguiente apunte a NULO.

2. Después que ultimo.siguiente apunte a DATO (último.nodo).

3. Y actualizamos último, haciendo que apunte a nodo.

1.2.1.3 Agregar elemento en una cola, caso general


Para generalizar el caso anterior, sólo necesitamos agregar una operación:

1. Hacemos que nodo.siguiente apunte a NULO.

2. Si último no es NULO, hacemos que ultimo.siguiente apunte a nodo.

3. Y actualizamos último, haciendo que apunte a nodo.

4. Si primero es NULO, significa que la cola estaba vacía, así que haremos que primero apunte también
a nodo.

1.2.2 Eliminar un elemento de una cola


Ahora también existen dos casos, que la cola tenga un solo elemento o que tenga más de uno.

1.2.2.1 Eliminar un elemento en una cola con más de un elemento


Usaremos un puntero a un nodo auxiliar:
Cola con más de un elemento
1. Hacemos que nodo apunte al primer elemento de la cola, es decir a primero.

2. Asignamos a primero la dirección del segundo nodo de la pila: primero.siguiente.

3. Guardamos el contenido del nodo para devolverlo como retorno, recuerda que la operación de
lectura en colas implican también borrar.

4. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.

Fuente: Propia

7
1.2.2.2 Eliminar un elemento en una cola con un solo elemento

Cola con un elemento


También necesitamos un puntero a un nodo auxiliar:

1. Hacemos que nodo apunte al primer elemento de la pila, es decir a primero.

Fuente: Propia

2. Asignamos NULO a primero, que es la dirección del segundo nodo teórico de la cola: primero.
siguiente.

3. Guardamos el contenido del nodo para devolverlo como retorno, recuerda que la operación de
lectura en colas implican también borrar.

4. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.

5. Hacemos que último apunte a NULO, ya que la lectura ha dejado la cola vacía.

1.2.2.3 Eliminar un elemento en una cola caso general

1. Hacemos que nodo apunte al primer elemento de la pila, es decir a primero.

2. Asignamos a primero la dirección del segundo nodo de la pila: primero.siguiente.

3. Guardamos el contenido del nodo para devolverlo como retorno, recuerda que la operación de
lectura en colas implican también borrar.

4. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.

5. Si primero es NULO, hacemos que ultimo también apunte a NULO, ya que la lectura ha dejado
la cola vacía.

8
Colas en java usando vectores. Ejemplo
import javax.swing.*;
public class Colas {
public static void main (String args[]){
byte max=9,op=0;
int i=0,fr=-1,fin=-1;
int V []= new int[10];
while(op!=4){
op=Byte.parseByte(JOptionPane.showInputDialog(“Digite un numero\n”+
“1.Insertar\n”+
“2.Eliminar\n”+
“3.Mostrar\n”+
“4.Salir”));
switch(op){
case 1:
if(fin!=max){
if(fr==-1){
fin=fin+1;
fr=fr+1;
V[fin]= Byte.parseByte(JOptionPane.showInputDialog(“Digite el valor”));
}else{
fin=fin+1;
V[fin]= Byte.parseByte(JOptionPane.showInputDialog(“Digite el valor”));
}
}else{
JOptionPane.showMessageDialog(nulo, “Overflow”);
}
break;
case 2:
if(fin!=-1){
if(fin==fr){
fin=-1;
fr=-1;
}else{
fr=fr+1;}
}else{
JOptionPane.showMessageDialog(nulo, “Underflow”);
}
break;
case 3:
if(fin!=-1){
for(i=fr;i<=fin;i=i+1){
JOptionPane.showMessageDialog(nulo,V[i]);
}
} else{
JOptionPane.showMessageDialog(nulo, “Cola vacia”);
}

break;
case 4:
JOptionPane.showMessageDialog(nulo, “Gracias”);
break;
}
}
}
}

9
1.3 Aplicaciones del uso de Colas

Las colas se pueden utilizar habitualmente en muchas áreas de la informática. Quizás la aplicación más
común de las colas es la organización de tareas de un ordenador. En general, los trabajos enviados a
un ordenador son “encolados” por éste, para ir procesando secuencialmente todos los trabajos en el
mismo orden en que se reciben. Cuando el ordenador recibe el encargo de realizar una tarea, ésta es
almacenada al final de la cola de trabajos. En el momento que la tarea que estaba realizando el procesador
acaba, éste selecciona la tarea situada al principio de la cola para ser ejecutada a continuación. Todo
esto suponiendo la ausencia de prioridades en los trabajos. En caso contrario, existirá una cola para
cada prioridad.

Otra de los usos de una cola es, por ejemplo, a la hora de gestionar eficientemente los trabajos que
deben ser enviados a una impresora (o a casi cualquier dispositivo conectado a un computador). De
esta manera, la maquina controla el envío de trabajos al dispositivo, no enviando un trabajo hasta que
la impresora no termine con el anterior

1.4 Cola circular

Una cola circular o anillo es una estructura de datos en la que los elementos están de forma circular
y cada elemento tiene un sucesor y un predecesor. Los elementos pueden consultarse, agregarse y
eliminarse únicamente desde la cabeza del anillo que es una posición distinguida. Esta avanza en el
sentido de las agujas del reloj.

Fuente: Propia

En la figura mostrada muestra una cola circular con un solo dato almacenado. La variable final es la
posición en donde se hizo la última inserción. Después que se ha producido una inserción, final se
mueve circularmente a la derecha. La implementación del movimiento circular se realiza utilizando la
“teda” de los restos:

10
Programa en Java cola circular

import javax.swing.*;
public class Colas_Circulares {
public static void main(String args[]){

int OP=0;
int VH[]= new int [4];
int Max=3;
int Last=-1, First=-1;
String A= “\n”;

while(OP!=5){
OP=Integer.parseInt(JOptionPane.showInputDialog(“Menu de Operaciones\n”+”1.Insertar\n”+”2.Elimi
nar\n”+”3.Imprimir\n”+”4.Imprimir First & Last\n”+”5.Salir\n”+”Escoja Opcion”));
switch(OP){
case 1:
if((Last==Max && First==0)||(First==Last+1)){
JOptionPane.showMessageDialog(nulo, “La Cola esta Llena”);

}else if(First==-1 && Last==-1){


Last++; First++;
VH[Last]= Integer.parseInt(JOptionPane.showInputDialog(“Ingrese Datos”));
}else if(First!=-1 && Last!=Max){
Last++;
VH[Last]= Integer.parseInt(JOptionPane.showInputDialog(“Ingrese Datos”));
}else if(Last==Max && First!=0){
Last=0;
}
break;
case 2:
if(First==-1 && Last==-1){
JOptionPane.showMessageDialog(nulo, “La Cola Esta Vacia”);
}else if(First==Last){
Last=-1;
First=-1;
JOptionPane.showMessageDialog(nulo, “Se ha Eliminado el Unico Elemento en la cola”);
}else if(First!=Last){
First++;
JOptionPane.showMessageDialog(nulo, “Se ha Eliminado el Primer Elemento de la Cola”);
}else if(Last==Max){
Last=0;

}
break;
case 3:
if(Last!=-1 && First!=-1){
for(int i=First;i<=Last;i++){
A=A+VH[i]+”,”;

}
JOptionPane.showMessageDialog(nulo, “Los Elementos de la Cola son: “ + A);
}else if(Last!=Max){
for(int j=Last;j<Last;j++){
A=A+VH[j]+”,”;
}

11
for(int x=First;x<=Max;x++){
A=A+VH[x]+”,”;
}
JOptionPane.showMessageDialog(nulo, “Los Elemetos de la Cola son: “ + A);

}else{
JOptionPane.showMessageDialog(nulo, “La Cola Esta Vacia”);
}
break;

case 4:
if(Last!=-1 && First!=-1){
JOptionPane.showMessageDialog(nulo, “El Primer Elemento de la Cola es: “ +VH[First]+ “ El Ultimo
Elemento de la Cola es: “ +VH[Last]);

}else{
JOptionPane.showMessageDialog(nulo, “La Cola Esta Vacia”);
}
break;
case 5:
JOptionPane.showMessageDialog(nulo, “Gracias Por Utilizar Nuestro Programa, EXITOS!!!”);

break;
default:
JOptionPane.showMessageDialog(nulo, “Ha Digitado una Opcion Fuera de Rango”);
}
}
}
}

1.5 Ejercicios Colas


1. Escriba una rutina que reciba una Cola C de números enteros y mueva sus elementos a una nueva
cola, pero manteniendo el orden de salida de los elementos. Al finalizar la Cola C no debe contener
elementos.

2. Escriba una rutina que reciba una Cola C de números enteros y mueva sus elementos a una nueva cola,
pero invirtiendo el orden de salida de los elementos. Al finalizar la Cola C no debe contener elementos.
3. Escriba una rutina que reciba dos Colas C1 y C2 de números enteros y devuelva una nueva Cola con
los elementos concatenados en el orden C1 y C2. Es de destacar que las Colas recibidas no deben ser
sufrir ningún tipo de cambio o alteración.
4. Escriba una rutina que reciba dos Colas C1 y C2 de números enteros y proceda a intercambiar sus
elementos, pero manteniendo el orden de salida de los mismos. Al finalizar la rutina, la Cola C1 tendrá
los elementos de la Cola C2 y esta a su vez tendrá los elementos de la Cola C1.
5. Escriba una rutina que reciba una Cola C de números flotantes y devuelva una nueva Cola pero con
los elementos invertidos, es decir el último de la Cola C, pasará a ser el primero de la nueva Cola. Es de
destacar que la Cola C no debe sufrir ningún tipo de cambio o alteración.
6. Escriba una rutina que reciba una Cola C de números flotantes y devuelva una Pila, manteniendo el
orden de salida de los elementos. Es de destacar que la Cola C no debe sufrir ningún tipo de cambio o
alteración.

12
7. Escribir un programa en el que generen 100 números aleatorios en el rango -25…. + 25 y se guarden
en una cola. Una vez creada la cola, el usuario puede solicitar que se forme otra cola con los números
negativos que tiene la cola original.

8. Escribir un programa que ingrese dos colas del mismo tipo e indique si las dos colas son idénticas.

9. Un pequeño supermercado dispone en la salida de tres cajas de pago. En el local hay 25 carritos de
compra. Escribir un programa que simule el funcionamiento, siguiendo las siguientes reglas:
a. SI cuando llega un cliente no hay ningún carrito disponible, espera a que lo haya.
b. Ningún cliente se impaciente y abandona el supermercado sin pasar por alguna de las colas de las
cajas.
c. Cuando un cliente finaliza su compra, se coloca en la cola de la caja que hay menos gente, y no se
cambia de cola.
d. En el momento en que un cliente paga en la caja, su carrito de compra queda disponible.

2. PILAS
2.1 Definición
Una pila es un tipo especial de lista abierta en la que sólo se pueden insertar y eliminar nodos en uno de
los extremos de la lista. Estas operaciones se conocen como “push” y “pop”, respectivamente insertar
y eliminar. Además, las escrituras de datos siempre son inserciones de nodos, y las lecturas siempre
eliminan el nodo leído.

Estas características implican un comportamiento de lista LIFO (Last In First Out), el último en entrar es
el primero en salir.

El símil del que deriva el nombre de la estructura es una pila de platos. Sólo es posible agregar platos en
la parte superior de la pila, y sólo pueden tomarse del mismo extremo.

Fuente: Propia

Una pila es una lista abierta. Así que sigue siendo muy importante que nuestro programa nunca pierda
el valor del puntero al primer elemento, igual que pasa con las listas abiertas.

Teniendo en cuenta que las inserciones y borrados en una pila se hacen siempre en un extremo, lo que
consideramos como el primer elemento de la lista es en realidad el último elemento de la pila.

2.2 Operaciones básicas con pilas


Las pilas tienen un conjunto de operaciones muy limitado, sólo permiten las operaciones de “push” y
“pop”:

· Push (Insertar): Agregar un elemento al final de la pila.

· Pop (Eliminar): Leer y eliminar un elemento del final de la pila.

13
2.2.1 Inserción de elementos
Las operaciones con pilas son muy simples, no hay casos especiales, salvo que la pila esté vacía.

2.2.1.1 Inserción en una pila vacía


Partiremos de que ya tenemos el nodo a insertar y, por supuesto un puntero que apunte a él, además
el puntero a la pila valdrá NULO:

Fuente: Propia

Inserción en pila vacía


El proceso es muy simple, bastará con que:

1. nodo.siguiente apunte a NULO.

2. Pila apunte a nodo.

2.2.1.2 Insertar en una pila no vacía

Fuente: Propia

Push en pila no vacía


Podemos considerar el caso anterior como un caso particular de éste, la única diferencia es que podemos
y debemos trabajar con una pila vacía como con una pila normal.
De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una pila, en este caso
no vacía:

Fuente: Propia

Resultado
El proceso sigue siendo muy sencillo:

1. Hacemos que nodo.siguiente apunte a Pila.


2. Hacemos que Pila apunte a nodo.

14
2.2.2 Eliminación en una Pila

Eliminar (pop)
Ahora sólo existe un caso posible, ya que sólo podemos leer desde un extremo de la pila.

Partiremos de una pila con uno o más nodos, y usaremos un puntero auxiliar, nodo:

Fuente: Propia

Resultado
1. Hacemos que nodo apunte al primer elemento de la pila, es decir a Pila.

2. Asignamos a Pila la dirección del segundo nodo de la pila: Pila.siguiente.

3. Guardamos el contenido del nodo para devolverlo como retorno, recuerda que la operación pop
equivale a leer y borrar.

4. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.

Si la pila sólo tiene un nodo, el proceso sigue siendo válido, ya que el valor de Pila.siguiente es NULO, y
después de eliminar el último nodo la pila quedará vacía, y el valor de Pila será NULO.

15
Pilas en java ejemplo
import javax.swing.*;
public class Pilas {
public static void main (String args[]){
byte max=9,op=0;
int tope=-1,i=0;
byte V []= new byte[10];
while(op!=4){
op=Byte.parseByte(JOptionPane.showInputDialog(“Digite un numero\n”+
“1.Insertar\n”+
“2.Eliminar\n”+
“3.Mostrar\n”+
“4.Salir”));
switch(op){
case 1:
if(tope!=max){
tope=tope+1;
V[tope]=Byte.parseByte(JOptionPane.showInputDialog(“Digite su valor”));
}else{
JOptionPane.showMessageDialog(nulo, “Overflow”);
}
break;
case 2:
if(tope!=-1){
tope=tope-1;
}else{
JOptionPane.showMessageDialog(nulo, “Underflow”);
}
break;
case 3:
if(tope!=-1){
for(i=tope;i>=0;i=i-1){
JOptionPane.showMessageDialog(nulo,V[i]);
}
} else{
JOptionPane.showMessageDialog(nulo, “Pila vacia”);
}
break;
case 4:
JOptionPane.showMessageDialog(nulo, “Gracias”);
break;
}
}
}
}

16
2.5 Aplicaciones del uso de Pilas
Con la implementación de las pilas es posible el uso de la modulación (recursividad). La variable que
llama al mismo procedimiento en el que está, habrá que guardarla así como el resto de variables de la
nueva llamada, para a la vuelta de la recursividad ir sacándolas, esto es posible a la implementación de
pilas.
Las pilas también se utilizan en muchas aplicaciones que utilizamos con frecuencia. Por ejemplo, la gestión
de ventanas en Windows (cuando cerramos una ventana siempre recuperamos la que teníamos detrás). Otro
ejemplo es la evaluación general de cualquier expresión matemática para evitar tener que calcular el número de
variables temporales que hacen falta.

Por ejemplo:
3 + 4 * (8 – 2 * 5)

2.6 Ejercicios con Pilas


1. Escriba una rutina que reciba una Pila P de números enteros y mueva sus elementos a una nueva Pila, pero
manteniendo el orden de salida de los mismos. Al finalizar la Pila P no debe contener elementos.
2. Escriba una rutina que reciba una Pila P de números enteros y mueva sus elementos a una nueva Pila, pero
invirtiendo el orden de salida de los mismos. Al finalizar la Pila P no debe contener elementos.
3. Escriba una rutina que reciba dos Pilas P1 y P2 de números flotantes y apile las mismas en una nueva Pila
resultante. Es de destacar que las Pilas recibidas no deben sufrir ningún tipo de cambio o alteración.
4. Escriba una rutina que reciba dos Pilas P1 y P2 de números enteros y proceda a intercambiar sus elementos,
pero manteniendo el orden de salida de los elementos. Al finalizar la rutina, la Pila P1 tendrá los elementos
de la Pila P2 y esta a su vez tendrá los elementos de la Pila P1.
5. Escriba una rutina que reciba una Pila P de números enteros y devuelva una copia exacta de la misma. Es de
destacar que la Pila P no debe sufrir ningún tipo de cambio o alteración.
6. Escriba una rutina que reciba una Pila P de números flotantes y devuelva una nueva Pila pero con los
elementos invertidos, es decir el último de la Pila P, pasará a ser el primero de la nueva Pila Es de destacar
que la Pila P no debe sufrir ningún tipo de cambio o alteración.
7. Escriba una rutina que reciba una Pila P de números flotantes y devuelva otra pila, manteniendo el orden
de salida de los elementos. Es de destacar que la Pila P no debe sufrir ningún tipo de cambio o alteración.
8. En un archivo f están almacenados números enteros arbitrariamente grandes. La disposición es tal que hay
un número entero por cada línea de F. Escribir un programa que muestre por pantalla la suma de todos los
números enteros. Al resolver el problema habrá que tener en cuenta que, al ser enteros grandes, no pueden
almacenarse en variables numéricas.
9. Utilizar dos pilas para guardar los dos primeros números enteros, almacenándose digito a dígito. Al extraer
los elementos de la pila, salen en orden inverso y, por lo tanto, de menor peso a mayor peso; se suman
digito con digito y el resultado se guarda en una cola, también digito a digito. A partir de este primer paso
se obtienen el siguiente número del archivo, se guarda en una pila y, a continuación, se suma digito a dígito
con el número que se encuentra en la cola; el resultado se guarda en otra cola. El proceso se repite, nuevo
número del archivo se mete en la pila, que se suma con el número actual de la cola.

17
Ejercicios Complementarios Pilas y Colas

1. Realizar un procedimiento que cuente la cantidad de elementos de una cola. Lo mismo para una
pila y una lista. Las estructuras deben quedar en el estado original.
2. Realizar un procedimiento que invierta el orden de la cola. Lo mismo para una pila y una lista.
3. Realizar un procedimiento que saque el elemento N de la cola. Lo mismo para una pila y una lista.
Tener en cuenta que los demás elementos deben quedar en el mismo orden.
4. Realizar un procedimiento que ingrese un elemento en la posición N de una cola. Lo mismo para
una pila y una lista. Tener en cuenta que los demás elementos deben quedar en el mismo orden.
5. Una cola prioridad tiene una estructura similar a la de una cola, con la diferencia que cada elemento
tiene un campo que indica su prioridad. Los elementos se ingresan en la cola según el orden de
prioridad. Realizar dos procedimientos, uno para ingresar un elemento en la cola prioridad y otro
para extraerlo. Utilizar para ello un TDA COLA.

3. LISTAS ENLAZADAS
3.1 Definición
La forma más simple de estructura dinámica es la lista abierta. En esta forma los nodos se organizan
de modo que cada uno apunta al siguiente, y el último no apunta a nada, es decir, el puntero del nodo
siguiente vale NULO.

En las listas abiertas existe un nodo especial: el primero. Normalmente diremos que nuestra lista es un
puntero a ese primer nodo y llamaremos a ese nodo la cabeza de la lista. Eso es porque mediante ese
único puntero podemos acceder a toda la lista.

Cuando el puntero que usamos para acceder a la lista vale NULO, diremos que la lista está vacía.

El nodo típico para construir listas tiene esta forma:


struct nodo {
int dato;
nodo *siguiente;
}
3.2 Declaraciones de tipos para manejar listas
Normalmente se definen varios tipos que facilitan el manejo de las listas, la declaración de tipos puede
tener una forma parecida a esta:
struct nodo {
int dato;
nodo *siguiente;
}

Lista es el tipo para declarar listas, como puede verse, un puntero a un nodo y una lista son la misma
cosa. En realidad, cualquier puntero a un nodo es una lista, cuyo primer elemento es el nodo apuntado.

18
3.3 Operaciones básicas con listas
Con las listas tendremos unas operaciones básicas que se pueden realizar:
· Agregar o insertar elementos.
· Buscar o localizar elementos.
· Borrar elementos.
· Moverse a través de una lista, anterior, siguiente, primero.
Cada una de estas operaciones tendrá varios casos especiales, por ejemplo, no será lo mismo insertar
un nodo en una lista vacía, o al principio de una lista no vacía, o la final, o en una posición intermedia.
3.3.1 Insertar elementos en una lista abierta
Algunos casos de inserción en una lista se verán a continuación.
3.3.1.1 Insertar un elemento en una lista vacía
Este es uno de los casos más sencillo. Partiremos de que ya tenemos el nodo a insertar y, por supuesto
un puntero que apunte a él, además el puntero a la lista valdrá NULO:

Fuente: Propia
El proceso es muy simple, bastará con que:
1. nodo.siguiente apunte a NULO.
2. Lista apunte a nodo.

3.3.1.2 Insertar un elemento en la primera posición de una lista

Fuente: Propia
Podemos considerar el caso anterior como un caso particular de éste, la única diferencia es que en el
caso anterior la lista es una lista vacía, pero siempre podemos, y debemos considerar una lista vacía
como una lista.
De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una lista, en este caso
no vacía:

Fuente: Propia
Insertado al principio de la lista
El proceso sigue siendo muy sencillo:
1. Hacemos que nodo.siguiente apunte a Lista.
2. Hacemos que Lista apunte a nodo.

19
3.3.1.3 Insertar un elemento en la última posición de una lista
Este es otro caso especial. Para este caso partiremos de una lista no vacía:

Fuente: Propia

Insertar al final
El proceso en este caso tampoco es excesivamente complicado:

1. Necesitamos un puntero que señale al último elemento de la lista. La manera de conseguirlo es


empezar por el primero y avanzar hasta que el nodo que tenga como siguiente el valor NULO.

2. Hacer que nodo.siguiente sea NULO.

3. Hacer que ultimo.siguiente sea nodo.

Fuente: Propia

3.3.1.4 Insertar un elemento a continuación de un nodo cualquiera de una lista


De nuevo podemos considerar el caso anterior como un caso particular de este. Ahora el nodo “anterior”
será aquel a continuación del cual insertaremos el nuevo nodo:
Insertar en medio
Suponemos que ya disponemos del nuevo nodo a insertar, apuntado por nodo, y un puntero al nodo a
continuación del que lo insertaremos.

El proceso a seguir será:

1. Hacer que nodo.siguiente señale a anterior.siguiente.

2. Hacer que anterior.siguiente señale a nodo.

Fuente: Propia

Es muy importante que el programa nunca pierda el valor del puntero al primer elemento, ya que si no
existe ninguna copia de ese valor, y se pierde, será imposible acceder al nodo y no podremos liberar el
espacio de memoria que ocupa.

20
3.3.2 Eliminar elementos en una lista abierta

De nuevo podemos encontrarnos con varios casos, según la posición del nodo a eliminar.
3.3.2.1 Eliminar el primer nodo de una lista abierta

Eliminar primer nodo


Es el caso más simple. Partiremos de una lista con uno o más nodos, y usaremos un puntero auxiliar,
nodo:

1. Hacemos que nodo apunte al primer elemento de la lista, es decir a Lista.

2. Asignamos a Lista la dirección del segundo nodo de la lista: Lista.siguiente.

3. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.

Si no guardamos el puntero al primer nodo antes de actualizar Lista, después nos resultaría imposible
liberar la memoria que ocupa. Si liberamos la memoria antes de actualizar Lista, perderemos el puntero
al segundo nodo.

Fuente: Propia

Si la lista sólo tiene un nodo, el proceso es también válido, ya que el valor de Lista.siguiente es NULO, y
después de eliminar el primer nodo la lista quedará vacía, y el valor de Lista será NULO.

Una operación que se suele usar para borrar listas completas es eliminar el primer nodo hasta que la
lista esté vacía.
3.3.2.2 Eliminar un nodo cualquiera de una lista abierta
En todos los demás casos, eliminar un nodo se puede hacer siempre del mismo modo. Supongamos que
tenemos una lista con al menos dos elementos, y un puntero al nodo anterior al que queremos eliminar.
Y un puntero auxiliar nodo.

El proceso es parecido al del caso anterior:


1. Hacemos que nodo apunte al nodo que queremos borrar.
2. Ahora, asignamos como nodo siguiente del nodo anterior, el siguiente al que queremos eliminar:
anterior.sig = nodo.sig

3. Eliminamos la memoria asociada al nodo que queremos eliminar.

Fuente: Propia
Si el nodo a eliminar es el último, es procedimiento es igualmente válido, ya que anterior pasará a ser el
último, y anterior.siguiente valdrá NULO.

21
Listas Enlazadas en java ejemplo.
import javax.swing.*;
public class p1 {
static class estudiante{
String nombre;
float nota;
estudiante liga;
}
public static void main (String args[]){
estudiante app=nulo,aux=nulo,aux1=nulo,aux2=nulo;
byte op=0;
String refn;
while(op!=7){
op=Byte.parseByte(JOptionPane.showInputDialog(
“Digite un su opcion\n”+
“1.Crear lista\n”+
“2.Insertar al principio\n”+
“3.Insertar antes de una referencia\n”+
“4.Insertar despues de una referencia\n”+
“5.Eliminar con referencia\n”+
“6.Imprimir\n” +
“7.Salir”));
switch(op){
case 1:
if(app==nulo){
app=new estudiante();
app.nombre=JOptionPane.showInputDialog(“Ingrese el noombre del estudiante”);
app.nota=Float.parseFloat(JOptionPane.showInputDialog(“Digite la nota”));
app.liga=nulo;
}else{
JOptionPane.showMessageDialog(nulo, “La lista ya ha sido creada”);
}
break;
case 2:
if(app!=nulo){
aux=new estudiante();
aux.nombre=JOptionPane.showInputDialog(“Ingrese el noombre del estudiante”);
aux.nota=Float.parseFloat(JOptionPane.showInputDialog(“Digite la nota”));
aux.liga=app;
app=aux;
}else{
JOptionPane.showMessageDialog(nulo, “La lista no ha sido creada”);
}
break;
case 3:
if (app!=nulo){
refn=JOptionPane.showInputDialog(“ingrese en nombre del estudiantes”);
aux=app;
while(!aux.nombre.equals(refn)){
aux2=aux;
aux=aux.liga;}
aux1=new estudiante();
aux1.nombre=JOptionPane.showInputDialog(“ingrese en nombre del estudiantes”);
aux1.nota=Float.parseFloat(JOptionPane.showInputDialog(“Digite la nota”));
}else{
JOptionPane.showMessageDialog(nulo, “La lista no ha sido creada”);
}

22
break;
case 4:
if(app!=nulo){
aux=app;
refn=JOptionPane.showInputDialog(“Ingrese el nombre del estudiante”);
while(!aux.nombre.equals(refn)){
aux=aux.liga;
}
aux1= new estudiante();
app.nombre=JOptionPane.showInputDialog(“Ingrese el noombre del estudiante”);
app.nota=Float.parseFloat(JOptionPane.showInputDialog(“Digite la nota”));
aux1.liga=aux.liga;
aux.liga=aux1;
}else{
JOptionPane.showMessageDialog(nulo, “La lista no ha sido creada”);
}
break;
case 5:
if(app==nulo){
aux=app;
refn=JOptionPane.showInputDialog(“Ingrese el nombre del estudiante”);
while(!aux.nombre.equals(refn)){
aux2=aux;
aux=aux.liga;
}
aux2.liga=aux.liga;
aux=nulo;
}
break;
case 6:
aux=app;
while(aux.liga!=nulo){
JOptionPane.showMessageDialog(nulo, aux.nombre+” “+ aux.nota);
aux=aux.liga;
}
break;
case 7:
JOptionPane.showMessageDialog(nulo, “Gracias por utilizar el programa”);
break;
}
}
}
}

23
3.4 LISTAS DOBLEMENTE ENLADAS (Listas dobles)
Las listas doblemente enlazadas son estructuras de datos similares a las listas simples. La asignación de
memoria es hecha al momento de la ejecución, la diferencia radica en que el enlace entre los elementos
se hace con dos apuntadores (uno que apunta hacia el nodo anterior y otro apunta hacia el siguiente
nodo).

Fuente: Propia

El puntero anterior del primer elemento debe apuntar hacia NULO.  


El puntero siguiente del último elemento debe apuntar hacia NULO.
Para acceder a un elemento, la lista puede ser recorrida en ambos sentidos:
· comenzando por el inicio, el apuntador  siguiente  permite el desplazamiento hacia el próximo
elemento.
· comenzando por el final, el apuntador anterior permite el desplazamiento hacia el elemento anterior.
El nodo típico para construir listas dobles tiene esta forma:
struct nodo {
int dato;
nodo *siguiente;
nodo *anterior;
}
Para controlar la lista se conservan algunos elementos: el primer
elemento, el último elemento y posiblemente el número de elementos. 
Para ello, se pueden utilizar dos punteros principales:
El puntero inicio contendrá la dirección del primer elemento de la lista. 
El puntero fin contendrá la dirección del último elemento de la lista. 
Cualquiera que sea la posición en la lista, los punteros inicio y fin apuntan siempre al primer y último
elemento. 

3.4.1 Operaciones básicas con listas doblemente enlazadas


Las operaciones con listas doblemente enlazas son muy similares a las de listas simples, por lo tanto solo se
indicara los pasos a seguir en cada una de ellas.

3.4.1.1 Insertar un elemento en una lista doble

Antes de cualquier otra operación sobre la lista, se inicializa el puntero inicio y el puntero fin con el valor NULO.
Para agregar un elemento a la lista hay varias situaciones:
1. Inserción en una lista vacía
2. Inserción al inicio de la lista
3. Inserción al final de la lista
4. Inserción antes de un elemento
5. Inserción después de un elemento

24
3.4.1.1 Inserción en una lista vacía

Pasos:
· Asignación de memoria para el nuevo elemento
· Rellenar el campo de datos del nuevo elemento
· El puntero anterior del nuevo elemento apuntará hacia NULO (ya que la inserción es hecha en una
lista vacía utilizamos la dirección del puntero inicio que vale NULO)
· El puntero siguiente del nuevo elemento apuntará hacia NULO (ya que la inserción es hecha en una
lista vacía se utiliza la dirección del puntero fin que vale NULO)
· Los punteros inicio y fin apuntaran hacia el nuevo elemento
· El tamaño es actualizado

3.4.1.2 Inserción al inicio de la lista

Pasos:

· Asignación de memoria al nuevo elemento


· Rellenar el campo de datos del nuevo elemento
· El puntero anterior del nuevo elemento apunta hacia NULO
· El puntero siguiente del nuevo elemento apunta hacia el 1er elemento
· El puntero anterior del 1er elemento apunta hacia el nuevo elemento
· El puntero inicio apunta hacia el nuevo elemento
· El puntero fin no cambia
· El tamaño es incrementado

3.4.1.3 Inserción al final de la lista

Pasos:
· Asignación de memoria al nuevo elemento
· Rellenar el campo de datos del nuevo elemento
· El puntero siguiente del nuevo elemento apunta hacia NULO
· El puntero  anterior  del nuevo elemento apunta hacia el último elemento (es el puntero  fin  que
contiene por ahora su dirección)
· El puntero siguiente del último elemento apuntará hacia el nuevo elemento
· El puntero fin apunta hacia el nuevo elemento
· El puntero inicio no cambia

3.4.1.4 Inserción antes de un elemento de la lista

La inserción se efectuara antes de cierta posición pasado como argumento a la función. 


La posición indicada no debe ser ni el primer ni el último elemento. En ese
caso hay que utilizar las funciones de inserción al inicio y/o al final de la lista. 

25
Pasos:
· Asignación de memoria al nuevo elemento
· Rellenar el campo de datos del nuevo elemento
· Elegir una posición en la lista (la inserción se hará después de la posición elegida)
· El puntero siguiente del nuevo elemento apunta hacia el elemento actual.
· El puntero anterior del nuevo elemento apunta hacia la dirección hacia la que apunta el puntero
anterior del elemento actual.
· El puntero siguiente del elemento que precede al elemento actual apuntará hacia el nuevo elemento
· El puntero anterior del elemento actual apunta hacia el nuevo elemento
· El puntero fin no cambia
· El puntero inicio cambia si la posición elegida es la posición 1

3.4.1.5 Inserción después de un elemento de la lista

La posición indicada no debe ser el último elemento. En ese caso hay que utilizar la función de
inserción al final de la lista. 

Pasos:
· Asignación de memoria al nuevo elemento
· Rellenar el campo de datos del nuevo elemento
· Elegir una posición en la lista (la inserción se hará después de la posición elegida)
· El puntero  siguiente  del nuevo elemento apunta hacia la dirección hacia la que apunta el puntero
siguiente del elemento actual
· El puntero anterior del nuevo elemento apunta hacia el elemento actual.
· El puntero  anterior  del elemento que va después del elemento actual apuntará hacia el nuevo
elemento.
· El puntero siguiente del elemento actual apunta hacia el nuevo elemento
· El puntero inicio no cambia
· El puntero fin cambia si la posición elegida es la posición del último elemento (el tamaño de la lista)

3.4.2. Eliminación de un elemento de la lista

En comparación a la lista enlazada simple en el que la eliminación solo puede ser hecha después que un
elemento ha sido designado, las listas doblemente enlazadas son más flexibles gracias a los 2 punteros
que permiten guardar la ubicación de la lista, tanto hacia atrás como hacia delante.
Para eliminar un elemento de la lista hay varias situaciones:

· 1. Eliminación al inicio de la lista


· 2. Eliminación al final de la lista
· 3. Eliminación antes de un elemento
· 4. Eliminación después de un elemento
· 5 Eliminación de un elemento

26
En el caso de listas doblemente enlazadas la eliminación en cualquier posición no presenta ningún
problema gracias a los punteros  anterior  y  siguiente, que permiten conservar el enlace entre los
elementos de la lista.

· Si deseamos eliminar el elemento al inicio de la lista será el elemento apuntado por inicio.
· Si deseamos eliminar el elemento al inicio de la lista será el elemento apuntado por final.
· Si deseamos eliminar cualquier elemento entonces elegimos su posición en la lista.

3.4.2.1 Eliminación en la lista doble del primer elemento

Pasos:

· El puntero inicio contendrá la dirección contenida por el puntero siguiente del primer elemento


que deseamos eliminar (si este puntero vale NULO entonces actualizamos el puntero fin ya que
estamos en el caso de una lista con un solo elemento, si no hacemos apuntar el puntero anterior
del segundo elemento hacia NULO).

3.4.2.2 Eliminación en la lista doble del último elemento

Pasos:

· El puntero fin contendrá la dirección del último elemento.


· Hacemos apuntar al puntero  siguiente  del penúltimo elemento (es el elemento hacia el que
apunta el puntero anterior del último elemento), hacia NULO.
· Actualizamos el puntero fin.

3.4.2.3 Eliminación en la lista doble de cualquier elemento

Pasos:

· El puntero sup_elemento contendrá la dirección del elemento a eliminar.


· El puntero siguiente del elemento que antecede al elemento a eliminar apunta hacia la dirección
contenida en el puntero siguiente del elemento a eliminar.
· El puntero anterior del elemento que va después del elemento a eliminar apunta hacia la dirección
contenida en el puntero anterior del elemento a eliminar.

27
3.5 Ejercicios con Listas enlazadas
1. Implementar una lista enlazada de datos enteros con las funciones básicas
2. Implementar una lista enlazada de datos enteros que calcule el mayor de los datos e indique la
posición en que se encuentre.
3. Implementar una lista enlazada de datos enteros que calcule el dato mínimo y cuente la cantidad de
veces que se repita.
4. Implementar una lista enlazada de datos enteros que sume los datos de una lista recursivamente.
5. Implementar una lista enlazada de datos enteros que sume los datos pares de una lisa recursivamente.
6. Implementar una lista enlazada de datos enteros que visualice los datos pares de la lista recursivamente.
7. Implementar una lista enlazada de datos enteros que muestre los números primos.
8. Implementar una lista enlazada de datos enteros que ordene los datos de la lista.
9. Implementar una lista enlazada de datos enteros que verifique si esta ordenada.
10. Implementar una lista enlazada de datos enteros que la invierta.
11. Implementar una lista enlazada de datos enteros que busque un dato de la lista recursivamente.
12. Implementar una lista enlazada de datos enteros que elimine un dato recursivamente.
13. Implementar una lista enlazada de datos enteros que duplique los datos de la lista y los almacene
en otra lista

4. Recursividad
4.1 Concepto de recursividad.

• La recursividad es una técnica de programación muy potente que puede ser utilizada en lugar de la
iteración.
• Permite diseñar algoritmos recursivos que dan soluciones elegantes y simples, y generalmente bien
estructuradas y modulares, a problemas de gran complejidad.
Es una técnica que nos permite que un algoritmo se invoque a sí mismo para resolver una versión más
pequeña del problema original que le fue encomendado
public void f_rec(...)

{

f_rec(…)

}

El caso es que las definiciones recursivas aparecen con frecuencia en matemáticas, e incluso en la vida
real. Un ejemplo: basta con apuntar una cámara al monitor que muestra la imagen que muestra esa
cámara. El efecto es verdaderamente curioso, en especial cuando se mueve la cámara alrededor del
monitor.
En matemáticas, tenemos múltiples definiciones recursivas:
- Números naturales:
(1) 1 es número natural.
(2) el siguiente número de un número natural es un número natural

28
- El factorial: n!, de un número natural (incluido el 0):
(1) si n = 0 entonces: 0! = 1
(2) si n > 0 entonces: n! = n · (n-1)!

Asimismo, puede definirse un programa en términos recursivos, como una serie de pasos básicos, o
paso base (también conocido como condición de parada), y un paso recursivo, donde vuelve a llamarse
al programa. En una computadora, esta serie de pasos recursivos debe ser finita, terminando con un
paso base. Es decir, a cada paso recursivo se reduce el número de pasos que hay que dar para terminar,
llegando un momento en el que no se verifica la condición de paso a la recursividad. Ni el paso base ni
el paso recursivo son necesariamente únicos.
Por otra parte, la recursividad también puede ser indirecta, si tenemos un procedimiento P que llama a
otro Q y éste a su vez llama a P. También en estos casos debe haber una condición de parada.

4.2 Ejemplos
a. Factorial.
En matemáticas es frecuente definir un concepto en función del proceso usado para generarlo. Por
ejemplo, una descripción matemática de n! es:
n! = 1 si n = 0
n⋅(n-1) ⋅....⋅1 si n > 0
También se puede definir de forma más sencilla:
n! = 1 si n = 0
n⋅(n-1)! si n > 0
Esta segunda definición es recursiva, puesto que se expresa un método factorial en términos de sí
mismo. A partir de esta definición, es fácil obtener un algoritmo recursivo para calcular el factorial de
un número natural n.
public static int factorialR(int n) {
int fact;
if (n==0)
fact=1;
else
fact=n*factorial(n-1);
return fact ;
}

De esto se pueden sacar varias conclusiones:

1. El método se invoca a sí mismo (esto es lo que lo convierte en recursivo).


2. Cada llamada recursiva se hace con un parámetro de menor valor que el de la anterior llamada.
Así, cada vez se está invocando a otro problema idéntico pero de menor tamaño.
3. Existe un caso degenerado en el que se actúa de forma diferente, esto es, ya no se utiliza la
recursividad. Lo importante es que la forma en la que el tamaño del problema disminuye asegura
que se llegará a este caso degenerado o caso base. Esto es necesario, porque de lo contrario se
estaría ejecutando indefinidamente.

29
• Para determinar si un método recursivo está bien diseñado se utiliza el método de las tres preguntas:

1. La pregunta Caso-Base: Existe una salida no recursiva o caso base del y éste funciona correctamente
para él?
2. La pregunta Invocar más pequeño Cada llamada recursiva se refiere a un caso más pequeño del
problema original?
3. La pregunta Caso-General. Suponiendo que la(s) llamada(s) recursiva(s) funciona(n) correctamente,
así como el caso base, funciona correctamente todo el método?.

Si por ejemplo, le aplicamos esto al método factorial(n), podemos comprobar cómo las respuestas a las
3 preguntas son afirmativas, con lo que podemos deducir que el algoritmo recursivo está bien diseñado.

b. Multiplicación de conejos.

Supongamos que partimos de una pareja de conejos recién nacidos, y queremos calcular cuántas parejas
de conejos forman la familia al cabo de n meses si:

1. Los conejos nunca mueren.


2. Un conejo puede reproducirse al comienzo del tercer mes de vida.
3. Los conejos siempre nacen en parejas macho-hembra. Al comienzo de cada mes, cada pareja macho-
hembra, sexualmente madura, se reproduce en exactamente un par de conejos macho-hembra. Para
una n pequeña, por ejemplo 6, la solución se puede obtener fácilmente a mano:

Mes 1: 1 pareja, la inicial.


Mes 2: 1 pareja, ya que todavía no es sexualmente madura.
Mes 3: 2 parejas; la original y una pareja de hijos suyos.
Mes 4: 3 parejas; la original, una pareja de hijos suyos nacidos ahora y la pareja de hijos suyos nacidos
en el mes anterior.
Mes 5: 5 parejas; la original, una pareja de hijos suyos nacidos ahora, las dos parejas nacidas en los
meses 3 y 4, y una pareja de hijos de la pareja nacida en el mes 3.
Mes 6: 8 parejas; las 5 del mes anterior, una pareja de hijos de la original, una pareja de hijos de la
nacida en el mes 3 y una pareja de hijos nacida en el mes 4.

Si deseamos saber el número de parejas al cabo de n meses, para un n cualquiera, podemos construir
un algoritmo recursivo fácilmente a partir de la siguiente relación:
Parejas(n) = 1 si n <= 2
Parejas(n-1)+Parejas(n-2) si n > 2

En esta relación Parejas(n-1) son las parejas vivas en el mes n-1, y Parejas(n-2) son las parejas que nacen
en el mes n a partir de las que había en el mes n-2.

La serie de números Parejas (1), Parejas (2), Parejas (3),... es conocida como la Serie de Fibonacci, la cual
modela muchos fenómenos naturales.

30
La función recursiva quedaría:

public static int parejas(int n){


int par;
if (n<=2)
par=1;
else
par=parejas(n-1)+parejas(n-2);
return par;
}

c. Recursión indirecta.

A continuación se expone un ejemplo de programa que utiliza recursión indirecta, y nos dice si un
número es par o impar. Al igual que el programa anterior, hay otro método mucho más sencillo de
determinar si un número es par o impar, basta con determinar el resto de la división entre dos. Por
ejemplo: si hacemos par(2) devuelve (cierto). Si hacemos impar(4) devuelve (falso).
public static boolean par(int n)
{
if (n == 0) return true;
return impar(n-1);
}

public static boolean impar(int n)


{
if (n == 0) return false;
return par(n-1);
}

Ejemplo: Si hacemos la llamada impar(3) hace las siguientes llamadas:


par(2)
impar(1)
par(0) . devuelve 1 (cierto)

Por lo tanto 3 es un número impar.

Qué pasa si se hace una llamada recursiva que no termina?

Cada llamada recursiva almacena los parámetros que se pasaron al procedimiento, y otras variables
necesarias para el correcto funcionamiento del programa. Por tanto si se produce una llamada recursiva
infinita, esto es, que no termina nunca, llega un momento en el que no quedará memoria para almacenar
más datos, y en ese momento se abortará la ejecución del programa. Para probar esto se puede intentar
hacer esta llamada en el programa factorial definido anteriormente:

factorialR(-1);
Por supuesto no hay que pasar parámetros a una función que estén fuera de su dominio, pues el
factorialR está definido solamente para números naturales, pero es un ejemplo claro.

31
4.3 Uso de la recursividad.
Se evidenció que la recursión es una técnica potente de programación para resolver, mediante
soluciones simples y claras, problemas de incluso gran complejidad. Sin embargo, la recursión también
tiene algunas desventajas desde el punto de vista de la eficiencia. Muchas veces un algoritmo iterativo
es más eficiente que su correspondiente recursivo. Existen dos factores que contribuyen a ello

a. La sobrecarga asociada con las llamadas a los métodos.


• Este factor es propio de los métodos en general, aunque es mayor con la recursividad, ya que una
simple llamada inicial a un método puede generar un gran número de llamadas recursivas.
• Aunque el uso de la recursión, como herramienta de modularidad, puede clarificar programas
complejos que compensaría la sobrecarga adicional, no debemos usar la recursividad por el gusto de
usarla. Por ejemplo, el método factorialR presentada antes no debería usarse en la práctica.
Se puede fácilmente escribir una función iterativa Factorial tan clara como la recursiva y sin la sobrecarga
de ésta.
public int factorial(int n){
int cont, fact;
fact=1;
for (cont=2; cont<=n; cont++)
fact=fact*cont;
return fact;
}
Podemos encontrar algunas diferencias interesantes entre FactorialR y FactorialI, que se presentarán
por lo general entre métodos recursivos e iterativos.

• El método iterativo utiliza una construcción cíclica (for, while,…) para controlar la ejecución, mientras
que la solución recursiva utiliza una estructura de selección.
• La versión iterativa necesita un par de variables locales, mientras que la versión recursiva sólo una.

b. La ineficiencia inherente de algunos métodos recursivos.

Esta ineficiencia es debida al método empleado para resolver el problema concreto que se esté
abordando.

Por ejemplo, en el método presentado para resolver el problema de la “multiplicación de conejos”,


apreciamos un gran inconveniente: los mismos valores son calculados varias veces. Por ejemplo, para
calcular Parejas R(7), tenemos que calcular ParejasR(3) cinco veces. Mientras más grande sea n, mayor
ineficiencia.
Se puede construir una solución iterativa basada en la misma relación, que actúa hacia delante en lugar
de hacia atrás, y calcula cada valor sólo una vez.

32
public int parejasII(int n){
int R, R1, R2, i;
R1=1;
R2=1;
R=1;
for (i=3; i<=n; i++){
R=R1+R2;
R2=R1;
R1=R;
}
return R;
}

El hecho de que la recursividad tenga estas dos desventajas, no quiere decir que sea una mala técnica y
que no se deba utilizar, sino que hay que usarla cuando realmente sea necesaria:

El verdadero valor de la recursividad es como herramienta para resolver problemas para los que no hay
soluciones iterativas simples.

¿Cuándo utilizar la recursión?

No se debe utilizar cuando la solución iterativa sea clara a simple vista. Sin embargo, en otros casos,
obtener una solución iterativa es mucho más complicado que una solución recursiva, y es entonces
cuando se puede plantear la duda de si merece la pena transformar la solución recursiva en otra
iterativa. En realidad la recursión se basa en almacenar en una pila los valores de las variables locales
que haya para un procedimiento en cada llamada recursiva. Esto reduce la claridad del programa. Aun
así, hay que considerar que el compilador transformará la solución recursiva en una iterativa, utilizando
una pila, para cuando compile al código de la computadora.

Por otra parte, casi todos los esquemas de vuelta atrás y divide y vencerás son recursivos, pues de
alguna manera parece mucho más natural una solución recursiva.

En general mucho más sencillo escribir un programa recursivo que su equivalente iterativo.

33
4.4 Ejercicios
1. Escriba una definición recursiva de una función que tiene un parámetro n de tipo entero y que
devuelve el n-ésimo número de Fibonacci. Los números de Fibonacci se definen de la siguiente manera:
F0 = 1
F1 = 1
Fi+2 = Fi + Fi+1
2. La forma para calcular cuantas maneras diferentes tengo para elegir r cosas distintas de un conjunto
de n cosas es:
C(n,r) = n! (r!*(n-r)!)
Donde la función factorial se define como
n! = n *(n-1)*(n-2)*…*2*1
Descubra una versión recursiva de la fórmula anterior y escriba una función recursiva que calcule el
valor de dicha fórmula.
3. Escriba una función recursiva que ordene de menor a mayor un arreglo de enteros basándose en la
siguiente idea: coloque el elemento más pequeño en la primera ubicación, y luego ordene el resto del
arreglo con una llamada recursiva.
4. Escribir una función recursiva que devuelva la suma de los primeros N enteros
5. Escribir un programa que encuentre la suma de los enteros positivos pares desde N hasta 2. Chequear
que si N es impar se imprima un mensaje de error.
6. Escribir un programa que calcule el máximo común divisor (MCD) de dos enteros positivos. Si M >= N
una función recursiva para MCD es
MCD = M si N =0
MCD = MCD (N, M mod N) si N <> 0
El programa le debe permitir al usuario ingresar los valores para M y N desde la consola. Una función
recursiva es entonces llamada para calcular el MCD. El programa entonces imprime el valor para el
MCD. Si el usuario ingresa un valor para M que es < que N el programa es responsable de switchear los
valores.
7. Programe un método recursivo que transforme un número entero positivo a notación binaria.
8. Programe un método recursivo que transforme un número expresado en notación binaria a un
número entero.
9. Programe un método recursivo que calcule la suma de un arreglo de números enteros.
10. Programe un método recursivo que invierta los números de un arreglo de enteros.
11. Cuál es el resultado de esta función para distintos valores de X?
Static int f(int x)
{
if (x >100)
{
return (x-10);
}
else
{
return(f(f(x+11)));
}
}
12. Implemente una función recursiva que nos diga si una cadena es palíndromo.

34
5. Árboles
5.1 Definición
Un árbol es una estructura no lineal en la que cada nodo puede apuntar a uno o varios nodos.

También se suele dar una definición recursiva: un árbol es una estructura en compuesta por un dato y
varios árboles.

Esto son definiciones simples. Pero las características que implican no lo son tanto.

Fuente: Propia

Definiremos varios conceptos. En relación con otros nodos:

· Nodo hijo: cualquiera de los nodos apuntados por uno de los nodos del árbol. En el ejemplo, ‘L’
y ‘M’ son hijos de ‘G’.

· Nodo padre: nodo que contiene un puntero al nodo actual. En el ejemplo, el nodo ‘A’ es padre
de ‘B’, ‘C’ y ‘D’.

Los árboles con los que trabajaremos tienen otra característica importante: cada nodo sólo puede ser
apuntado por otro nodo, es decir, cada nodo sólo tendrá un padre. Esto hace que estos árboles estén
fuertemente jerarquizados, y es lo que en realidad les da la apariencia de árboles.

En cuanto a la posición dentro del árbol:

· Nodo raíz: nodo que no tiene padre. Este es el nodo que usaremos para referirnos al árbol. En el
ejemplo, ese nodo es el ‘A’.

· Nodo hoja: nodo que no tiene hijos. En el ejemplo hay varios: ‘F’, ‘H’, ‘I’, ‘K’, ‘L’, ‘M’, ‘N’ y ‘O’.

· Nodo rama: aunque esta definición apenas la usaremos, estos son los nodos que no pertenecen
a ninguna de las dos categorías anteriores. En el ejemplo: ‘B’, ‘C’, ‘D’, ‘E’, ‘G’ y ‘J’.

Otra característica que normalmente tendrán nuestros árboles es que todos los nodos contengan el
mismo número de punteros, es decir, usaremos la misma estructura para todos los nodos del árbol. Esto
hace que la estructura sea más sencilla, y por lo tanto también los programas para trabajar con ellos.
Tampoco es necesario que todos los nodos hijos de un nodo concreto existan. Es decir, que pueden
usarse todos, algunos o ninguno de los punteros de cada nodo.

35
Un árbol en el que en cada nodo o bien todos o ninguno de los hijos existe, se llama árbol completo.

En una cosa, los árboles se parecen al resto de las estructuras que hemos visto: dado un nodo cualquiera
de la estructura, podemos considerarlo como una estructura independiente. Es decir, un nodo cualquiera
puede ser considerado como la raíz de un árbol completo.

Existen otros conceptos que definen las características del árbol, en relación a su tamaño:

· Orden: es el número potencial de hijos que puede tener cada elemento de árbol. De este modo,
diremos que un árbol en el que cada nodo puede apuntar a otros dos es de orden dos, si puede
apuntar a tres será de orden tres, etc.

· Grado: el número de hijos que tiene el elemento con más hijos dentro del árbol. En el árbol del
ejemplo, el grado es tres, ya que tanto ‘A’ como ‘D’ tienen tres hijos, y no existen elementos con
más de tres hijos.

· Nivel: se define para cada elemento del árbol como la distancia a la raíz, medida en nodos. El
nivel de la raíz es cero y el de sus hijos uno. Así sucesivamente. En el ejemplo, el nodo ‘D’ tiene
nivel 1, el nodo ‘G’ tiene nivel 2, y el nodo ‘N’, nivel 3.

· Altura: la altura de un árbol se define como el nivel del nodo de mayor nivel. Como cada nodo
de un árbol puede considerarse a su vez como la raíz de un árbol, también podemos hablar de
altura de ramas. El árbol del ejemplo tiene altura 3, la rama ‹B› tiene altura 2, la rama ‹G› tiene
altura 1, la ‹H› cero, etc.

Los árboles de orden dos son bastante especiales, de hecho les dedicaremos varios capítulos. Estos
árboles se conocen también como árboles binarios.

Frecuentemente, aunque tampoco es estrictamente necesario, para hacer más fácil moverse a través
del árbol, agregaremos un puntero a cada nodo que apunte al nodo padre. De este modo podremos
avanzar en dirección a la raíz, y no sólo hacia las hojas.

Es importante conservar siempre el nodo raíz ya que es el nodo a partir del cual se desarrolla el árbol,
si perdemos este nodo, perderemos el acceso a todo el árbol.

El nodo típico de un árbol difiere de los nodos que hemos visto hasta ahora para listas, aunque sólo en
el número de nodos. Veamos un ejemplo de nodo para crear árboles de orden tres:
struct nodo {
int dato;
struct nodo *rama1;
struct nodo *rama2;
struct nodo *rama3;
}
5.2 Operaciones básicas con árboles
Salvo que trabajemos con árboles especiales, como los que veremos más adelante, las inserciones serán
siempre en punteros de nodos hoja o en punteros libres de nodos rama. Con estas estructuras no es tan
fácil generalizar, ya que existen muchas variedades de árboles.

36
De nuevo tenemos casi el mismo repertorio de operaciones de las que disponíamos con las listas:
· Agregar o insertar elementos.
· Buscar o localizar elementos.
· Borrar elementos.
· Moverse a través del árbol.
· Recorrer el árbol completo.

Los algoritmos de inserción y borrado dependen en gran medida del tipo de árbol que estemos
implementando, de modo que por ahora los pasaremos por alto y nos centraremos más en el modo de
recorrer árboles.
5.3 Recorridos por árboles
El modo evidente de moverse a través de las ramas de un árbol es siguiendo los punteros, del mismo
modo en que nos movíamos a través de las listas.
Esos recorridos dependen en gran medida del tipo y propósito del árbol, pero hay ciertos recorridos que
usaremos frecuentemente. Se trata de aquellos recorridos que incluyen todo el árbol.
Hay tres formas de recorrer un árbol completo, y las tres se suelen implementar mediante recursividad.
En los tres casos se sigue siempre a partir de cada nodo todas las ramas una por una.
Supongamos que tenemos un árbol de orden tres, y queremos recorrerlo por completo.
Partiremos del nodo raíz:
RecorrerArbol(raiz);
La función RecorrerArbol, aplicando recursividad, será tan sencilla como invocar de nuevo a la función
RecorrerArbol para cada una de las ramas:
void RecorrerArbol(Arbol a) {
if(a == NULO) return;
RecorrerArbol(a.rama[0]);
RecorrerArbol(a.rama[1]);
RecorrerArbol(a.rama[2]);
}
Lo que diferencia los distintos métodos de recorrer el árbol no es el sistema de hacerlo, sino el momento
que elegimos para procesar el valor de cada nodo con relación a los recorridos de cada una de las ramas.
Los tres tipos son: Pre-orden, In-orden y Post-orden.

5.3.1 Pre-orden
En este tipo de recorrido, el valor del nodo se procesa antes de recorrer las ramas:
void PreOrden(Arbol a) {
if(a == NULO) return;
Procesar(dato);
RecorrerArbol(a.rama[0]);
RecorrerArbol(a.rama[1]);
RecorrerArbol(a.rama[2]);
}
Si seguimos el árbol del ejemplo en pre-orden, y el proceso de los datos es sencillamente mostrarlos por
pantalla, obtendremos algo así:
ABEKFCGLMDHIJNO

37
5.3.2 In-orden
En este tipo de recorrido, el valor del nodo se procesa después de recorrer la primera rama y antes
de recorrer la última. Esto tiene más sentido en el caso de árboles binarios, y también cuando existen
ORDEN-1 datos, en cuyo caso procesaremos cada dato entre el recorrido de cada dos ramas (este es el
caso de los árboles-b):
void InOrden(Arbol a) {
if(a == NULO) return;
RecorrerArbol(a.rama[0]);
Procesar(dato);
RecorrerArbol(a.rama[1]);
RecorrerArbol(a.rama[2]);
}
Si seguimos el árbol del ejemplo en in-orden, y el proceso de los datos es sencillamente mostrarlos por
pantalla, obtendremos algo así:
KEBFALGMCHDINJO

5.3.3 Post-orden
En este tipo de recorrido, el valor del nodo se procesa después de recorrer todas las ramas:
void PostOrden(Arbol a) {
if(a == NULO) return;
RecorrerArbol(a.rama[0]);
RecorrerArbol(a.rama[1]);
RecorrerArbol(a.rama[2]);
Procesar(dato);
}
Si seguimos el árbol del ejemplo en post-orden, y el proceso de los datos es sencillamente mostrarlos
por pantalla, obtendremos algo así:
KEFBLMGCHINOJDA

5.4 Eliminar nodos en un árbol


El proceso general es muy sencillo en este caso, pero con una importante limitación, sólo podemos
borrar nodos hoja:

El proceso sería el siguiente:


1. Buscar el nodo padre del que queremos eliminar.
2. Buscar el puntero del nodo padre que apunta al nodo que queremos borrar.
3. Liberar el nodo.
4. padre.nodo[i] = NULO;

38
Cuando el nodo a borrar no sea un nodo hoja, diremos que hacemos una “poda”, y en ese caso
eliminaremos el árbol cuya raíz es el nodo a borrar. Se trata de un procedimiento recursivo, aplicamos
el recorrido PostOrden, y el proceso será borrar el nodo.

El procedimiento es similar al de borrado de un nodo:

1. Buscar el nodo padre del que queremos eliminar.

2. Buscar el puntero del nodo padre que apunta al nodo que queremos borrar.

3. Podar el árbol cuyo padre es nodo.

4. padre.nodo[i] = NULO;

En el árbol del ejemplo, para podar la rama ‘B’, recorreremos el subárbol ‘B’ en postorden, eliminando
cada nodo cuando se procese, de este modo no perdemos los punteros a las ramas apuntadas por cada
nodo, ya que esas ramas se borrarán antes de eliminar el nodo.

De modo que el orden en que se borrarán los nodos será:


KEFyB
5.5 Árboles ordenados
A partir del siguiente capítulo sólo hablaremos de árboles ordenados, ya que son los que tienen más
interés desde el punto de vista de TAD, y los que tienen más aplicaciones genéricas.

Un árbol ordenado, en general, es aquel a partir del cual se puede obtener una secuencia ordenada
siguiendo uno de los recorridos posibles del árbol: inorden, preorden o postorden.

En estos árboles es importante que la secuencia se mantenga ordenada aunque se añadan o se eliminen
nodos.

Existen varios tipos de árboles ordenados, que veremos a continuación:

· Árboles binarios de búsqueda (ABB): son árboles de orden 2 que mantienen una secuencia
ordenada si se recorren en inorden.

· Árboles AVL: son árboles binarios de búsqueda equilibrados, es decir, los niveles de cada rama
para cualquier nodo no difieren en más de 1.

· Árboles perfectamente equilibrados: son árboles binarios de búsqueda en los que el número de
nodos de cada rama para cualquier nodo no difieren en más de 1. Son por lo tanto árboles AVL
también.

· Árboles 2-3: son árboles de orden 3, que contienen dos claves en cada nodo y que están también
equilibrados. También generan secuencias ordenadas al recorrerlos en inorden.

· Árboles-B: caso general de árboles 2-3, que para un orden M, contienen M-1 claves.

39
5.6 Árboles binarios de búsqueda (ABB)

5.6.1 Definición
Se trata de árboles de orden 2 en los que se cumple que para cada nodo, el valor de la clave de la raíz del
subárbol izquierdo es menor que el valor de la clave del nodo y que el valor de la clave raíz del subárbol
derecho es mayor que el valor de la clave del nodo.

Árbol binario de búsqueda


Fuente: Propia

5.6.2 Operaciones en ABB


El repertorio de operaciones que se pueden realizar sobre un ABB es parecido al que realizábamos
sobre otras estructuras de datos, más alguna otra propia de árboles:
· Buscar un elemento.
· Insertar un elemento.
· Borrar un elemento.
· Movimientos a través del árbol:
o Izquierda.
o Derecha.
o Raíz.

· Información:
o Comprobar si un árbol está vacío.
o Calcular el número de nodos.
o Comprobar si el nodo es hoja.
o Calcular la altura de un nodo.
o Calcular la altura de un árbol.

40
5.6.3 Buscar un elemento
Partiendo siempre del nodo raíz, el modo de buscar un elemento se define de forma recursiva.
· Si el árbol está vacío, terminamos la búsqueda: el elemento no está en el árbol.
· Si el valor del nodo raíz es igual que el del elemento que buscamos, terminamos la búsqueda
con éxito.
· Si el valor del nodo raíz es mayor que el elemento que buscamos, continuaremos la búsqueda
en el árbol izquierdo.
· Si el valor del nodo raíz es menor que el elemento que buscamos, continuaremos la búsqueda
en el árbol derecho.

El valor de retorno de una función de búsqueda en un ABB puede ser un puntero al nodo encontrado, o
NULO, si no se ha encontrado.

5.6.4 Insertar un elemento


Para insertar un elemento nos basamos en el algoritmo de búsqueda. Si el elemento está en el árbol no
lo insertaremos. Si no lo está, lo insertaremos a continuación del último nodo visitado.

Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raíz actual. El valor
inicial para ese puntero es NULO.
· Padre = NULO
· nodo = Raíz
· Bucle: mientras actual no sea un árbol vacío o hasta que se encuentre el elemento.
o Si el valor del nodo raíz es mayor que el elemento que buscamos, continuaremos la
búsqueda en el árbol izquierdo: Padre=nodo, nodo=nodo.izquierdo.
o Si el valor del nodo raíz es menor que el elemento que buscamos, continuaremos la
búsqueda en el árbol derecho: Padre=nodo, nodo=nodo.derecho.
· Si nodo no es NULO, el elemento está en el árbol, por lo tanto salimos.
· Si Padre es NULO, el árbol estaba vacío, por lo tanto, el nuevo árbol sólo contendrá el nuevo
elemento, que será la raíz del árbol.
· Si el elemento es menor que el Padre, entonces insertamos el nuevo elemento como un nuevo
árbol izquierdo de Padre.
· Si el elemento es mayor que el Padre, entonces insertamos el nuevo elemento como un nuevo
árbol derecho de Padre.

Este modo de actuar asegura que el árbol sigue siendo ABB.

41
5.6.5 Borrar un elemento

Para borrar un elemento también nos basamos en el algoritmo de búsqueda. Si el elemento no está en
el árbol no lo podremos borrar. Si está, hay dos casos posibles:

1. Se trata de un nodo hoja: en ese caso lo borraremos directamente.

2. Se trata de un nodo rama: en ese caso no podemos eliminarlo, puesto que perderíamos todos
los elementos del árbol de que el nodo actual es padre. En su lugar buscamos el nodo más a la
izquierda del subárbol derecho, o el más a la derecha del subárbol izquierdo e intercambiamos
sus valores. A continuación eliminamos el nodo hoja.

Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raíz actual. El valor
inicial para ese puntero es NULO.
· Padre = NULO
· Si el árbol está vacío: el elemento no está en el árbol, por lo tanto salimos sin eliminar ningún
elemento.
· (1) Si el valor del nodo raíz es igual que el del elemento que buscamos, estamos ante uno de los
siguientes casos:

o El nodo raíz es un nodo hoja:


§ Si ‘Padre’ es NULO, el nodo raíz es el único del árbol, por lo tanto el puntero al
árbol debe ser NULO.
§ Si raíz es la rama derecha de ‘Padre’, hacemos que esa rama apunte a NULO.
§ Si raíz es la rama izquierda de ‘Padre’, hacemos que esa rama apunte a NULO.
§ Eliminamos el nodo, y salimos.

o El nodo no es un nodo hoja:


§ Buscamos el ‘nodo’ más a la izquierda del árbol derecho de raíz o el más a la
derecha del árbol izquierdo. Hay que tener en cuenta que puede que sólo exista
uno de esos árboles. Al mismo tiempo, actualizamos ‘Padre’ para que apunte al
padre de ‘nodo’.
§ Intercambiamos los elementos de los nodos raíz y ‘nodo’.
§ Borramos el nodo ‘nodo’. Esto significa volver a (1), ya que puede suceder que
‘nodo’ no sea un nodo hoja. (Ver ejemplo 3)
· Si el valor del nodo raíz es mayor que el elemento que buscamos, continuaremos la búsqueda
en el árbol izquierdo.
· Si el valor del nodo raíz es menor que el elemento que buscamos, continuaremos la búsqueda
en el árbol derecho.

42
Ejemplo 1: Borrar un nodo hoja
En el árbol de ejemplo, borrar el nodo 10.
1. Localizamos el nodo a borrar, al tiempo que mantenemos un puntero a ‘Padre’.
2. Hacemos que el puntero de ‘Padre’ que apuntaba a ‘nodo’, ahora apunte a NULO.

3. Borramos el ‘nodo’.

Fuente: Propia

Borrar un nodo hoja

Ejemplo 2: Borrar un nodo rama con intercambio de un nodo hoja.


En el árbol de ejemplo, borrar el nodo 4.
1. Localizamos el nodo a borrar (‘raíz’).
2. Buscamos el nodo más a la derecha del árbol izquierdo de ‘raíz’, en este caso el 3, al tiempo que
mantenemos un puntero a ‘Padre’ a ‘nodo’.
3. Intercambiamos los elementos 3 y 4.
4. Hacemos que el puntero de ‘Padre’ que apuntaba a ‘nodo’, ahora apunte a NULO.
5. Borramos el ‘nodo’.

Fuente: Propia

43
Ejemplo 3: Borrar un nodo rama con intercambio de un nodo rama.
Para este ejemplo usaremos otro árbol. En éste borraremos el elemento 6.

Fuente: Propia
1. Localizamos el nodo a borrar (‘raíz’).
2. Buscamos el nodo más a la izquierda del árbol derecho de ‘raíz’, en este caso el 12, ya que el
árbol derecho no tiene nodos a su izquierda, si optamos por la rama izquierda, estaremos en un
caso análogo. Al mismo tiempo que mantenemos un puntero a ‘Padre’ a ‘nodo’.
3. Intercambiamos los elementos 6 y 12.
4. Ahora tenemos que repetir el bucle para el nodo 6 de nuevo, ya que no podemos eliminarlo.

Fuente: Propia

5. Localizamos de nuevo el nodo a borrar (‘raíz’).


6. Buscamos el nodo más a la izquierda del árbol derecho de ‘raíz’, en este caso el 16, al mismo
tiempo que mantenemos un puntero a ‘Padre’ a ‘nodo’.
7. Intercambiamos los elementos 6 y 16.
8. Hacemos que el puntero de ‘Padre’ que apuntaba a ‘nodo’, ahora apunte a NULO.
9. Borramos el ‘nodo’.

Fuente: Propia

Este modo de actuar asegura que el árbol sigue siendo ABB

44
Árboles ABB en Java.
import javax.swing.*;
public class NewClass {
static class nodo{
int dato;
nodo ligad;
nodo ligai;
}
public static void Insercion(int datoi,nodo aux) {
nodo aux1;
if(aux.dato>datoi){
if(aux.ligai==nulo){
aux1 =new nodo();
aux1.dato= datoi;
aux1.ligai=nulo;
aux1.ligad=nulo;
aux.ligai=aux1;
JOptionPane.showMessageDialog(nulo,”el dato ha sido insertado”);
}else{
aux=aux.ligai;
Insercion(datoi, aux);
}
}else{
if(aux.ligad==nulo){
aux1 =new nodo();
aux1.dato= datoi;
aux1.ligai=nulo;
aux1.ligad=nulo;
aux.ligad=aux1;
JOptionPane.showMessageDialog(nulo,”el dato ha sido insertado”);
}else{
aux=aux.ligad;
Insercion(datoi,aux);
}
}
}
public static void Busqueda(int datoi,nodo aux) {
if(aux.dato==datoi){
JOptionPane.showMessageDialog(nulo,”el numero existe”);
}else{
if(datoi<aux.dato){
if(aux.ligai==nulo){
JOptionPane.showMessageDialog(nulo, “el numero no existe”);
}else{
Busqueda(datoi,aux.ligai);
}
}else{
if(aux.ligad==nulo){
JOptionPane.showMessageDialog(nulo, “el numero no existe”);
}else{
Busqueda(datoi,aux.ligad);
}
}
}
}
public static void Eliminar(int datoi, nodo aux) {

45
nodo aux1,aux2;
aux1=aux.ligai;
aux2=aux.ligad;
if(aux1.dato!=datoi && aux2.dato!=datoi){
if(datoi<aux.dato){
if(aux1==nulo){
JOptionPane.showMessageDialog(nulo, “el numero no existe”);
}else{
Eliminar(datoi,aux1);
}
}else{
if(aux2==nulo){
JOptionPane.showMessageDialog(nulo, “el numero no existe”);
}else{
Eliminar(datoi,aux2);
}
}
}else{
if(aux1.dato==datoi){
aux.ligai=nulo;
aux1=nulo;
JOptionPane.showMessageDialog(nulo, “la hoja ha sido eliminada”);
}else{
if(aux2.dato==datoi){
aux.ligad=nulo;
aux2=nulo;
JOptionPane.showMessageDialog(nulo, “la hoja ha sido eliminada”);
}
}
}
}

public static void main (String args[]){


nodo raiz=nulo, aux=nulo ;
byte op=0;
int datoi;
while(op!=5){
op=Byte.parseByte(JOptionPane.showInputDialog(
“Digite un su opcion\n”+
“1.Crear Arbol\n”+
“2.Insertar \n”+
“3.Busqueda \n”+
“4.Eliminar\n” +
“5.Salir”));
switch(op){
//crear arbol
case 1:{
if(raiz==nulo){
raiz=new nodo();
raiz.dato=Integer.parseInt(JOptionPane.showInputDialog(“Ingrese el dato”));
raiz.ligad=nulo;
raiz.ligai=nulo;
}else{
JOptionPane.showMessageDialog(nulo, “El arbol ya ha sido creado”);
}
}

46
break;
//”2.Insertar
case 2:{
aux=raiz;
datoi=Integer.parseInt(JOptionPane.showInputDialog(“inserte el dato a insertar”));
Insercion(datoi,aux);
}
break;
//”3.Busqueda
case 3:{
aux=raiz;
datoi=Integer.parseInt(JOptionPane.showInputDialog(“inserte el dato a buscar”));
Busqueda(datoi,aux);
}
break;
//”4.Eliminar
case 4:{
aux=raiz;
datoi=Integer.parseInt(JOptionPane.showInputDialog(“inserte el dato de la hoja a eliminar”));
Eliminar(datoi,aux);
}
break;
//5.Salir
case 5:{
JOptionPane.showMessageDialog(nulo, “Gracias por utilizar el programa”);
break;
}
}
}
}
}

47
5.7 EJERCICIOS
1. Determine el nivel, la altura y el grado de cada nodo.

Fuente: Propia

2. Dados los conjuntos:


A = { 25, 34, 42, 21, 23, 18, 45, 22, 9, 35 }
B = { 50, 20, 80, 10, 70, 25, 85, 62, 79, 5 } 
a. Transfórmelo en ABB.
b. Elimine de A: {23, 34} y de B: {20, 70}.

3. Dado el siguiente árbol, señale su recorrido en In-Orden, Post-Orden y Pre-Orden.

Fuente: Propia

4. Dado el siguiente árbol, señale su recorrido en In-Orden, Post-Orden y Pre-Orden.

Fuente: Propia

48
6. Bibliografía de Referencia

· WEISS, M.A.: Data Structures and Algorithm Analysis in Java, 3rd Edition, Pearson/Addison Wesley, 2011.

· DEITEL, P.J. y DEITEl, H.M.: Java. Cómo programar (7ª edición), Prentice Hall, 2008.

· CAIRO,OSVALDO Y SILVIA, Guardati. Estructura de Datos, McGraw Hill. 2006

· HERNÁNDEZ, Z.J. y otros: Fundamentos de Estructuras de Datos. Soluciones en Ada, Java y C++, Thomson,
2005.

· MEHTA, D.P. Y SAHNI, S.: Handbook of Data Structures and Applications, Chapman & Hall/CRC, 2005.

· WEISS, M.A.: Estructuras de datos en Java, Pearson/Addison Wesley, 2000.

· FRANCH GUTIÉRREZ, X.: Estructuras de Datos. Especificación, Diseño e Implementación. 3ª edición, Ed.
Edicions UPC, 2001.

· CAIRO, Guardati. (1993). Estructura de Datos, Prentice-Hall. 267 p.

· JOYANES AGUILAR, Luís. (1996). Fundamentos de programación: algoritmos y estructura datos. McGraw-Hill.
234 p.

· JOYANES, L., ZAHONERO, I., FERNÁNDEZ, M. Y SÁNCHEZ, L.: Estructura de datos. Libro de problemas, McGraw
Hill, 1999.

49

Das könnte Ihnen auch gefallen