Está en la página 1de 53

M

a
n
u
a
l

d
e

E
S
t
r
u
c
t
u
r
a

d
e

D
a
t
o
s
EVELIO ARRIETA
INTRODUCCIN ......................................................................... 6
1.4 LEER UN ELEMENTO DE UNA COLA, IMPLICA
ELIMINARLO ...................................................................... 11
2. PILAS ........................................................................................ 13
2.1 DEFINICIN
2.2 OPERACIONES BSICAS CON PILAS ............................ 14
2.3 INSERTAR ELEMENTOS
2.4 LEER Y ELIMINAR ELEMENTO ........................................ 15
3. LISTAS ENLAZADAS ............................................................... 16
4. RBOLES ................................................................................. 23
3.1 DEFINICIN
4.1 DEFINICIN
4.2 OPERACIONES BSICAS CON RBOLES .................... 26
4.3 RECORRIDOS POR RBOLES ........................................ 27
3.2 DECLARACIONES DE TIPOS PARA
MANEJAR LISTAS ............................................................. 17
3.3 OPERACIONES BSICAS CON LISTAS
3.4 INSERTAR ELEMENTOS EN UNA LISTA ABIERTA ......... 18
3.5 ELIMINAR ELEMENTOS EN UNA LISTA ABIERTA .......... 21
1. COLAS ...................................................................................... 7
1.1 DEFINICIN
1.2 OPERACIONES BSICAS CON COLAS .......................... 8
1.3 AADIR UN ELEMNTO
4.4 ELIMINAR NODOS DE UN RBOL ................................... 30
4.5 RBOLES ORDENADOS .................................................. 31
4.6 RBOLES BINARIOS DE BSQUEDA (ABB) ................... 32
4. 6.1 DEFINICIN
4.6.2 OPERACIONES EN ABB ........................................ 33
4.6.3 BUSCAR UN ELEMENTO ....................................... 34
4.6.4 INSERTAR UN ELEMENTO
4.6.5 INSERTAR UN ELEMENTO .................................... 35
5. RECURSIVIDAD ....................................................................... 42
5.1 CONCEPTO DE RECURSIVIDAD
5.2 EJEMPLOS
5.3 USO DE LA RECURSIVIDAD ............................................ 49
5.4 EJERCICIOS ...................................................................... 53
4
INTRODUCCIN
En la prctica, la mayor parte de informacin til no aparece aislada en forma de datos simples,
sino que lo hace de forma organizada y estructurada. Los diccionarios, guas, enciclopedias,
etc., son colecciones de datos que seran intiles si no estuvieran organizadas de acuerdo con
unas determinadas reglas. Adems, tener estructurada la informacin supone ventajas adicio-
nales, al facilitar el acceso y el manejo de los datos. Por ello parece razonable desarrollar la
idea de la agrupacin de datos, que tengan un cierto tipo de estructura y organizacin interna.
La seleccin de una estructura de datos frente a otra, a la hora de programar es una decisin
importante, ya que ello infuye decisivamente en el algoritmo que vaya a usarse para resolver
un determinado problema. El objetivo de este captulo no es slo la descripcin de las distintas
estructuras, sino tambin la comparacin de las mismas en trminos de utilidad para la pro-
gramacin.
Empecemos recordando que un dato de tipo simple, no est compuesto de otras estructuras,
que no sean los bits, y que por tanto su representacin sobre el ordenador es directa, sin em-
bargo existen unas operaciones propias de cada tipo, que en cierta manera los caracterizan.
Una estructura de datos es, a grandes rasgos, una coleccin de datos (normalmente de tipo
simple) que se caracterizan por su organizacin y las operaciones que se defnen 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 nmeros reales),
como por las operaciones posibles en ella. Esto supone que
podamos expresar formalmente, mediante un conjunto de reglas, las relaciones y
operaciones posibles.
5
1. COLAS
1.1DEFINICIN
Una cola es un tipo especial de lista abierta en la que slo se pueden insertar nodos en uno
de los extremos de la lista y slo se pueden eliminar nodos en el otro. Adems, como sucede
con las pilas, las escrituras de datos siempre son inserciones de nodos, y las lecturas siempre
eliminan el nodo ledo.
Este tipo de lista es conocido como lista FIFO (First In First Out), el primero en entrar es el
primero en salir.
El smil cotidiano es una cola para comprar, por ejemplo, las entradas del cine. Los nuevos
compradores slo pueden colocarse al fnal de la cola, y slo el primero de la cola puede com-
prar la entrada.
El nodo tpico para construir pilas es el mismo que vimos en los captulos anteriores para la
construccin de listas:
struct nodo {
int dato;
struct nodo *siguiente;
}
6
Una cola, entonces, 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. Adems, debido al funcionamiento de las colas, tambin 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 ms fcil ser insertar nodos por el fnal, a continuacin del nodo que no tiene nodo
siguiente, y leerlos desde el principio, hay que recordar que leer un nodo implica eliminarlo de
la cola.
1.2 OPERACIONES BSICAS CON COLAS
1.3 AADIR UN ELEMENTO
Las operaciones con colas son muy sencillas, prcticamente no hay casos especiales, salvo
que la cola est vaca.
Aadir elemento en una cola vaca
De nuevo nos encontramos ante una estructura con muy pocas operaciones disponibles. Las
colas slo permiten aadir y leer elementos:
Aadir: Inserta un elemento al fnal de la cola.
Leer: Lee y elimina un elemento del principio de la cola.
7
Cola vaca
Partiremos de que ya tenemos el nodo a insertar y, por supuesto un puntero que apunte a l,
adems los punteros que defnen la cola, primero y ltimo que valdrn NULL:
Elemento encolado
El proceso es muy simple, bastar con que:
1. Hacer que nodo->siguiente apunte a NULL.
2. Que el puntero primero apunte a nodo.
3. Y que el puntero ltimo tambin apunte a nodo.
Aadir elemento en una cola no vaca
8
Cola no vaca
Elemento encolado
El proceso sigue siendo muy sencillo:
1. Hacemos que nodo->siguiente apunte a NULL.
2. Despus que ultimo->siguiente apunte a nodo.
3. Y actualizamos ltimo, haciendo que apunte a nodo.
Aadir elemento en una cola, caso general
Para generalizar el caso anterior, slo necesitamos aadir una operacin:
1. Hacemos que nodo->siguiente apunte a NULL.
2. Si ultimo no es NULL, hacemos que ultimo->siguiente apunte
a nodo.
3. Y actualizamos ltimo, haciendo que apunte a nodo.
4. Si primero es NULL, signifca que la cola estaba vaca, as que
haremos que primero apunte tambin a nodo.
9
1.4 LEER UN ELEMENTO DE UNA COLA, IMPLICA ELIMINARLO
Ahora tambin existen dos casos, que la cola tenga un solo elemento o que tenga ms de uno.
Leer un elemento en una cola con ms de un elemento
Usaremos un puntero a un nodo auxiliar:
Cola con ms de un elemento
1. Hacemos que nodo apunte al primer elemento de la cola, es
decir a primero.
2. Asignamos a primero la direccin del segundo nodo de la pila:
primero->siguiente.
3. Guardamos el contenido del nodo para devolverlo como re-
torno, recuerda que la operacin de lectura en colas implican
tambin borrar.
4. Liberamos la memoria asignada al primer nodo, el que quere-
mos eliminar.
10
Elemento desencolado
Leer un elemento en una cola con un solo elemento
Cola con un elemento
Tambin necesitamos un puntero a un nodo auxiliar:
1. Hacemos que nodo apunte al primer elemento de la pila, es decir a primero.
Elemento desencolado
2. .Asignamos NULL a primero, que es la direccin del segundo nodo terico
de la cola:
primero->siguiente
3. Guardamos el contenido del nodo para devolverlo como retorno, recuerda
que la
operacin de lectura en colas implican tambin borrar.
4. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.
5. Hacemos que ltimo apunte a NULL, ya que la lectura ha dejado la cola
vaca.
11
2. PILAS
2.1 DEFINICIN
Una pila es un tipo especial de lista abierta en la que slo se pueden insertar y eliminar nodos
en uno de los extremos de la lista. Estas operaciones se conocen como push y pop, res-
pectivamente empujar y tirar. Adems, las escrituras de datos siempre son inserciones de
nodos, y las lecturas siempre eliminan el nodo ledo.
Estas caractersticas implican un comportamiento de lista LIFO (Last In First Out), el ltimo en
entrar es el primero en salir.
El smil del que deriva el nombre de la estructura es una pila de platos. Slo es posible aadir
platos en la parte superior de la pila, y slo pueden tomarse del mismo extremo.
El nodo tpico para construir pilas es el mismo que vimos en el captulo anterior para la cons-
truccin de listas:
Una pila es una lista abierta. As que sigue siendo muy importante que nuestro programa nun-
ca 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 extre-
mo, lo que consideramos como el primer elemento de la lista es en realidad el ltimo elemento
de la pila.
struct nodo {
int dato;
nodo *siguiente;
}
12
2.2 OPERACIONES BSICAS CON PILAS
Push en una pila vaca
Push en vaca
Push en una pila no vaca
El proceso es muy simple, bastar con que:
Partiremos de que ya tenemos el nodo a insertar y, por supuesto un puntero que apunte a l,
adems el puntero a la pila valdr NULL:
2.3 INSERTAR ELEMENTO
Las operaciones con pilas son muy simples, no hay casos especiales, salvo que la pila est
vaca.
Las pilas tienen un conjunto de operaciones muy limitado, slo permiten las operaciones de
push y pop:
Push (Insertar): Aadir un elemento al fnal de la pila.
Pop(Eliminar): Leer y eliminar un elemento del fnal de la pila.
1. nodo->siguiente apunte a NULL.
2. Pilaa apunte a nodo.
13
Push en pila no vaca
2.4 LEER Y ELIMINAR UN ELEMENTO
Pop
Ahora slo existe un caso posible, ya que slo podemos leer desde un extremo de la pila.
Partiremos de una pila con uno o ms nodos, y usaremos un puntero auxiliar, nodo:
Podemos considerar el caso anterior como un caso particular de ste, la nica diferencia es
que podemos y debemos trabajar con una pila vaca 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 vaca:
Resultado
El proceso sigue siendo muy sencillo:
1. Hacemos que nodo->siguiente apunte a Pila.
2. Hacemos que Pila apunte a nodo.
14
Resultado
3. LISTAS ENLAZADAS
3.1 DEFINICIN
La forma ms simple de estructura dinmica 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 NULL.
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 NULL, diremos que la lista est
vaca.
El nodo tpico para construir listas tiene esta forma:
struct nodo {
int dato;
nodo *siguiente;
}
1. Hacemos que nodo apunte al primer elemento de la pila, es decir a Pila.
2. Asignamos a Pila la direccin del segundo nodo de la pila: Pila->siguiente.
3. Guardamos el contenido del nodo para devolverlo como retorno, recuerda
que la operacin pop equivale a leer y borrar.
4. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.
Si la pila slo tiene un nodo, el proceso sigue siendo vlido, ya que el valor de Pila->siguiente
es NULL, y despus de eliminar el ltimo nodo la pila quedar vaca, y el valor de Pila ser
NULL.
15
3.2 DECLARACIONES DE TIPOS PARA MANEJAR LISTAS
3.3 OPERACIONES BSICAS CON LISTAS
Normalmente se defnen varios tipos que facilitan el manejo de las listas, la declaracin de
tipos puede tener una forma parecida a esta:
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.
Con las listas tendremos un pequeo repertorio de operaciones bsicas que se pueden reali-
zar:
Aadir o insertar elementos.
Buscar o localizar elementos.
Borrar elementos.
Moverse a travs de una lista, anterior, siguiente, primero.
struct nodo {
int dato;
nodo *siguiente;
}

Cada una de estas operaciones tendr varios casos especiales, por ejemplo, no ser lo mis-
mo insertar un nodo en una lista vaca, o al principio de una lista no vaca, o la fnal, o en una
posicin intermedia.

16
3.4 INSERTAR ELEMENTOS EN UNA LISTA ABIERTA
Insertar un elemento en una lista vaca
Lista vaca
Insertar un elemento en la primera posicin de una lista
Insertar al principio
El proceso es muy simple, bastar con que:
1. nodo->siguiente apunte a NULL.
2. Lista apunte a nodo.
Veremos primero los casos sencillos y fnalmente construiremos un algoritmo genrico para la
insercin de elementos en una lista.
Este es, evidentemente, el caso ms sencillo. Partiremos de que ya tenemos el nodo a insertar
y, por supuesto un puntero que apunte a l, adems el puntero a la lista valdr NULL:
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 vaca, pero siempre podemos, y debemos conside-
rar una lista vaca como una lista.
17
Insertado al principio
Insertar un elemento en la ltima posicin de una lista
Insertar al fnal
El proceso en este caso tampoco es excesivamente complicado:
1. Necesitamos un puntero que seale al ltimo elemento de la lista. La ma-
nera de conseguirlo es empezar por el primero y avanzar hasta que el nodo
que tenga como siguiente el valor NULL.
2. Hacer que nodo->siguiente sea NULL.
3. Hacer que ultimo->siguiente sea nodo.
Este es otro caso especial. Para este caso partiremos de una lista no vaca:
El proceso sigue siendo muy sencillo:
1. Hacemos que nodo->siguiente apunte a Lista.
2. Hacemos que Lista apunte a nodo.
De nuevo partiremos de un nodo a insertar, con un puntero que apunte a l, y de una lista, en
este caso no vaca:
18
Insertado al fnal
Insertar dentro
Insertado dentro
Insertar un elemento a continuacin 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 continuacin del cual insertaremos el nuevo nodo:
Suponemos que ya disponemos del nuevo nodo a insertar, apuntado por nodo, y un puntero al
nodo a continuacin del que lo insertaremos.
El proceso a seguir ser:
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.
1. Hacer que nodo->siguiente seale a anterior->siguiente.
2. Hacer que anterior->siguiente seale a nodo.
19
3.5 ELIMINAR ELEMENTOS EN UNA LISTA ABIERTA
Eliminar el primer nodo de una lista abierta
Eliminar primer nodo
De nuevo podemos encontrarnos con varios casos, segn la posicin del nodo a eliminar.
Es el caso ms simple. Partiremos de una lista con uno o ms 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 direccin 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, despus nos resultara
imposible liberar la memoria que ocupa. Si liberamos la memoria antes de actualizar Lista,
perderemos el puntero al segundo nodo.
20
Primer nodo eliminado
Eliminar un nodo cualquiera de una lista abierta
Eliminar un nodo
Nodo eliminado
El proceso es parecido al del caso anterior:
Si la lista slo tiene un nodo, el proceso es tambin vlido, ya que el valor de Lista->siguiente
es NULL, y despus de eliminar el primer nodo la lista quedar vaca, y el valor de Lista ser
NULL.
De hecho, el proceso que se suele usar para borrar listas completas es eliminar el primer nodo
hasta que la lista est vaca.
En todos los dems casos, eliminar un nodo se puede hacer siempre del mismo modo. Supon-
gamos que tenemos una lista con al menos dos elementos, y un puntero al nodo anterior al que
queremos eliminar. Y un puntero auxiliar nodo.
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->siguiente = nodo->siguiente.
3. Eliminamos la memoria asociada al nodo que queremos eliminar.
Si el nodo a eliminar es el ltimo, es procedimiento es igualmente vlido, ya que anterior pa-
sar a ser el ltimo, y anterior->siguiente valdr NULL.
21
rbol
Un rbol es una estructura no lineal en la que cada nodo puede apuntar a uno o varios nodos.
Tambin se suele dar una defnicin recursiva: un rbol es una estructura en compuesta por
un dato y varios rboles.
Esto son defniciones simples. Pero las caractersticas que implican no lo son tanto.
Defniremos varios conceptos. En relacin con otros nodos:
Nodo hijo: cualquiera de los nodos apuntados por uno de los nodos del r-
bol. 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.
4. RBOLES
4.1 DEFINICIN
Los rboles con los que trabajaremos tienen otra caracterstica importante: cada nodo slo
puede ser apuntado por otro nodo, es decir, cada nodo slo tendr un padre. Esto hace que
estos rboles estn fuertemente jerarquizados, y es lo que en realidad les da la apariencia de
rboles.
22
En cuanto a la posicin dentro del rbol:
Nodo raz: 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 defnicin apenas la usaremos, estos son los no-
dos que no pertenecen a ninguna de las dos categoras anteriores. En el
ejemplo: B, C, D, E, G y J.
Otra caracterstica que normalmente tendrn nuestros rboles es que todos los nodos con-
tengan el mismo nmero de punteros, es decir, usaremos la misma estructura para todos los
nodos del rbol. Esto hace que la estructura sea ms sencilla, y por lo tanto tambin los pro-
gramas 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.
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 de-
cir, un nodo cualquiera puede ser considerado como la raz de un rbol completo.
23
Orden: es el nmero 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 nmero de hijos que tiene el elemento con ms hijos dentro del r-
bol. En el rbol del ejemplo, el grado es tres, ya que tanto A como D tienen
tres hijos, y no existen elementos con ms de tres hijos.
Nivel: se defne para cada elemento del rbol como la distancia a la raz,
medida en nodos. El nivel de la raz es cero y el de sus hijos uno. As suce-
sivamente. 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 defne como el nivel del nodo de mayor nivel.
Como cada nodo de un rbol puede considerarse a su vez como la raz de
un rbol, tambin 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 captulos.
Estos rboles se conocen tambin como rboles binarios.
Frecuentemente, aunque tampoco es estrictamente necesario, para hacer ms fcil moverse a
travs del rbol, aadiremos un puntero a cada nodo que apunte al nodo padre. De este modo
podremos avanzar en direccin a la raz, y no slo hacia las hojas.
Existen otros conceptos que defnen las caractersticas del rbol, en relacin a su tamao:
24
Es importante conservar siempre el nodo raz 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 tpico de un rbol difere de los nodos que hemos visto hasta ahora para listas, aunque
slo en el nmero 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;
}
4.2 OPERACIONES BSICAS CON RBOLES
Salvo que trabajemos con rboles especiales, como los que veremos ms adelante, las in-
serciones sern siempre en punteros de nodos hoja o en punteros libres de nodos rama. Con
estas estructuras no es tan fcil generalizar, ya que existen muchas variedades de rboles.
De nuevo tenemos casi el mismo repertorio de operaciones de las que disponamos con las
listas:
Aadir o insertar elementos.
Buscar o localizar elementos.
Borrar elementos.
Moverse a travs del rbol.
Recorrer el rbol completo.
25
4.3 RECORRIDOS POR RBOLES
Los algoritmos de insercin 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 ms en el
modo de recorrer rboles.
El modo evidente de moverse a travs de las ramas de un rbol es siguiendo los punteros, del
mismo modo en que nos movamos a travs de las listas.
Esos recorridos dependen en gran medida del tipo y propsito del rbol, pero hay ciertos re-
corridos 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 re-
cursividad. 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.
void RecorrerArbol(Arbol a) {
if(a == NULL) return;
RecorrerArbol(a->rama[0]);
RecorrerArbol(a->rama[1]);
RecorrerArbol(a->rama[2]);
}
Partiremos del nodo raz:
Partiremos del nodo raz:
La funcin RecorrerArbol, aplicando recursividad, ser tan sencilla como invocar de nuevo a la
funcin RecorrerArbol para cada una de las ramas:
26
Lo que diferencia los distintos mtodos de recorrer el rbol no es el sistema de hacerlo, sino el
momento que elegimos para procesar el valor de cada nodo con relacin a los recorridos de
cada una de las ramas.
rbol
Los tres tipos son:
Pre-orden
En este tipo de recorrido, el valor del nodo se procesa antes de recorrer las ramas:
void PreOrden(Arbol a) {
if(a == NULL) return;
Procesar(dato);
RecorrerArbol(a->rama[0]);
RecorrerArbol(a->rama[1]);
RecorrerArbol(a->rama[2]);
}
27
Si seguimos el rbol del ejemplo en pre-orden, y el proceso de los datos es sencillamente mos-
trarlos por pantalla, obtendremos algo as:
A B E K F C G L M D H I J N O
In-orden
En este tipo de recorrido, el valor del nodo se procesa despus de recorrer la primera rama y
antes de recorrer la ltima. Esto tiene ms sentido en el caso de rboles binarios, y tambin
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 == NULL) 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 mos-
trarlos por pantalla, obtendremos algo as:
K E F B L M G C H I N O J D A
28
4.4 ELIMINAR NODOS EN UN RBOL
El proceso general es muy sencillo en este caso, pero con una importante limitacin, slo po-
demos borrar nodos hoja:
El proceso sera el siguiente:
El procedimiento es similar al de borrado de un nodo:
Cuando el nodo a borrar no sea un nodo hoja, diremos que hacemos una poda, y en ese caso
eliminaremos el rbol cuya raz es el nodo a borrar. Se trata de un procedimiento recursivo,
aplicamos el recorrido PostOrden, y el proceso ser borrar el nodo.
En el rbol del ejemplo, para podar la rama B, recorreremos el subrbol B en postorden, eli-
minando cada nodo cuando se procese, de este modo no perdemos los punteros a las ramas
apuntadas por cada nodo, ya que esas ramas se borrarn antes de eliminar el nodo.
De modo que el orden en que se borrarn los nodos ser:
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] = NULL;.
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] = NULL;.
K E F y B
29
4.5 RBOLES ORDENADOS
A partir del siguiente captulo slo hablaremos de rboles ordenados, ya que son los que tie-
nen ms inters desde el punto de vista de TAD, y los que tienen ms aplicaciones genricas.
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 aadan o
se eliminen nodos.
Existen varios tipos de rboles ordenados, que veremos a continuacin:
rboles binarios de bsqueda (ABB): son rboles de orden 2 que mantienen
una secuencia ordenada si se recorren en inorden.
rboles AVL: son rboles binarios de bsqueda equilibrados, es decir, los
niveles de cada rama para cualquier nodo no diferen en ms de 1.
rboles perfectamente equilibrados: son rboles binarios de bsqueda en
los que el nmero de nodos de cada rama para cualquier nodo no diferen
en ms de 1. Son por lo tanto rboles AVL tambin.
rboles 2-3: son rboles de orden 3, que contienen dos claves en cada nodo
y que estn tambin equilibrados. Tambin generan secuencias ordenadas
al recorrerlos en inorden.
rboles-B: caso general de rboles 2-3, que para un orden M, contienen M-1
claves.
30
4.6 RBOLES BINARIOS DE BSQUEDA (ABB)
4.6.1 DEFINICIN
Se trata de rboles de orden 2 en los que se cumple que para cada nodo, el valor de la clave
de la raz del subrbol izquierdo es menor que el valor de la clave del nodo y que el valor de la
clave raz del subrbol derecho es mayor que el valor de la clave del nodo.
rbol binario de bsqueda
31
4.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, ms alguna otra propia de rboles:
Buscar un elemento.
Insertar un elemento.
Borrar un elemento.
Movimientos a travs del rbol:
Informacin:
Izquierda.
Derecha.
Raiz.
Comprobar si un rbol est vaco.
Calcular el nmero de nodos.
Comprobar si el nodo es hoja.
Calcular la altura de un nodo.
Calcular la altura de un rbol
32
4.6.3 BUSCAR UN ELEMENTO
4.6.4 INSERTAR UN ELEMENTO
Partiendo siempre del nodo raz, el modo de buscar un elemento se defne de forma recursiva.
Para insertar un elemento nos basamos en el algoritmo de bsqueda. Si el elemento est en el
rbol no lo insertaremos. Si no lo est, lo insertaremos a continuacin del ltimo nodo visitado.
Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raz actual.
El valor inicial para ese puntero es NULL.
Si el rbol est vaco, terminamos la bsqueda: el elemento no est en el
rbol.
Si el valor del nodo raz es igual que el del elemento que buscamos, termi-
namos la bsqueda con xito.
Si el valor del nodo raz es mayor que el elemento que buscamos, continua-
remos la bsqueda en el rbol izquierdo.
Si el valor del nodo raz es menor que el elemento que buscamos, continua-
remos la bsqueda en el rbol derecho.
Padre = NULL
nodo = Raiz
Bucle: mientras actual no sea un rbol vaco o hasta que se encuentre el
elemento.
o Si el valor del nodo raz es mayor que el elemento que buscamos, conti-
nuaremos la bsqueda en el rbol izquierdo: Padre=nodo, nodo=nodo->iz-
quierdo.
El valor de retorno de una funcin de bsqueda en un ABB puede ser un puntero al nodo en-
contrado, o NULL, si no se ha encontrado
33
4.6.5 BORRAR UN ELEMENTO
Si el valor del nodo raz es menor que el elemento que buscamos, con-
tinuaremos la bsqueda en el rbol derecho: Padre=nodo, nodo=no-
do->derecho.
Este modo de actuar asegura que el rbol sigue siendo ABB.
Para borrar un elemento tambin nos basamos en el algoritmo de bsqueda. Si el elemento no
est en el rbol no lo podremos borrar. Si est, hay dos casos posibles:
Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raz actual.
El valor inicial para ese puntero es NULL.
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
perderamos todos los elementos del rbol de que el nodo actual es padre.
En su lugar buscamos el nodo ms a la izquierda del subrbol derecho, o
el ms a la derecha del subrbol izquierdo e intercambiamos sus valores. A
continuacin eliminamos el nodo hoja.
Padre = NULL
Si el rbol est vaco: el elemento no est en el rbol, por lo tanto salimos
sin eliminar ningn elemento.
(1) Si el valor del nodo raz es igual que el del elemento que buscamos, es-
tamos ante uno de los siguientes casos:
El nodo raz es un nodo hoja:
34
El nodo no es un nodo hoja:
Buscamos el nodo ms a la izquierda del rbol derecho de raz o el ms a
la derecha del rbol izquierdo. Hay que tener en cuenta que puede que slo
exista uno de esos rboles. Al mismo tiempo, actualizamos Padre para que
apunte al padre de nodo.
Intercambiamos los elementos de los nodos raz y nodo.
Borramos el nodo nodo. Esto signifca volver a (1), ya que puede suceder
que nodo no sea un nodo hoja. (Ver ejemplo 3)
Si el valor del nodo raz es mayor que el elemento que buscamos, continua-
remos la bsqueda en el rbol izquierdo.
Si el valor del nodo raz es menor que el elemento que buscamos, continua-
remos la bsqueda en el rbol derecho.
Si Padre es NULL, el nodo raz es el nico del rbol, por lo tanto el puntero
al rbol debe ser NULL.
Si raz es la rama derecha de Padre, hacemos que esa rama apunte a
NULL.
Si raz es la rama izquierda de Padre, hacemos que esa rama apunte a
NULL.
Eliminamos el nodo, y salimos.
35
Ejemplo 1: Borrar un nodo hoja
En el rbol de ejemplo, borrar el nodo 3.
1. Localizamos el nodo a borrar, al tiempo que mantenemos un puntero a Pa-
dre.
2. Hacemos que el puntero de Padre que apuntaba a nodo, ahora apunte a
NULL.
3. Borramos el nodo.
Borrar un nodo hoja
36
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 (raz).
2. Buscamos el nodo ms a la derecha del rbol izquierdo de raz, 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
NULL.
5. Borramos el nodo.
Borrar con intercambio de nodo hoja
37
Ejemplo 3: Borrar un nodo rama con intercambio de un nodo rama.
Para este ejemplo usaremos otro rbol. En ste borraremos el elemento 6.
1. Localizamos el nodo a borrar (raz).
2. Buscamos el nodo ms a la izquierda del rbol derecho de raz, en este
caso el 12, ya que el rbol derecho no tiene nodos a su izquierda, si opta-
mos por la rama izquierda, estaremos en un caso anlogo. 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 po-
demos eliminarlo.
rbol binario de bsqueda
38
Borrar con intercambio de nodo rama (1)
1. Localizamos de nuevo el nodo a borrar (raz).
2. Buscamos el nodo ms a la izquierda del rbol derecho de raz, en este
caso el 16, al mis mo tiempo que mantenemos un puntero a Padre a
nodo.
3. Intercambiamos los elementos 6 y 16.
4. Hacemos que el puntero de Padre que apuntaba a nodo, ahora apunte a
NULL.
5. Borramos el nodo.
Borrar con intercambio de nodo rama (2)
39
Este modo de actuar asegura que el rbol sigue siendo ABB
EJERCICIOS
1. Determine el nivel, la altura y el grado de cada nodo.
2. Dados los conjuntos:
3. Dado el siguiente rbol, seale su recorrido en In-Orden, Post-Orden y Pre-Orden.
A = { 25, 34, 42, 21, 23, 18, 45, 22, 9, 35 }
B = { 50, 20, 80, 10, 70, 25, 85, 62, 79, 5 }
a. Transfrmelo en ABB.
b. Elimine de A: {23, 34} y de B: {20, 70}.
40
4. Dado el siguiente rbol, seale su recorrido en In-Orden, Post-Orden y Pre-Orden.
5. RECURSIVIDAD
5.1 CONCEPTO DE RECURSIVIDAD.
La recursividad es una tcnica de programacin muy potente que puede ser
utilizada en lugar de la iteracin.
Permite disear algoritmos recursivos que dan soluciones elegantes y sim-
ples, y generalmente bien estructuradas y modulares, a problemas de gran
complejidad.
public void f_rec(...)

En qu consiste realmente la recursividad? Es una tcnica que nos permite que un algoritmo
se invoque a s mismo para resolver una versin ms pequea del problema original que le
fue encomendado
41
El caso es que las defniciones recursivas aparecen con frecuencia en matemticas, e incluso
en la vida real. Un ejemplo: basta con apuntar una cmara al monitor que muestra la imagen
que muestra esa cmara. El efecto es verdaderamente curioso, en especial cuando se mueve
la cmara alrededor del monitor.
En matemticas, tenemos mltiples defniciones recursivas:
Asimismo, puede defnirse un programa en trminos recursivos, como una serie de pasos b-
sicos, o paso base (tambin conocido como condicin de parada), y un paso recursivo, donde
vuelve a llamarse al programa. En una computadora, esta serie de pasos recursivos debe ser
fnita, terminando con un paso base. Es decir, a cada paso recursivo se reduce el nmero de
pasos que hay que dar para terminar, llegando un momento en el que no se verifca la condi-
cin de paso a la recursividad. Ni el paso base ni el paso recursivo son necesariamente nicos.
Por otra parte, la recursividad tambin puede ser indirecta, si tenemos un procedimiento P que
llama a otro Q y ste a su vez llama a P. Tambin en estos casos debe haber una condicin de
parada.
- Nmeros naturales:
(1) 1 es nmero natural.
(2) el siguiente nmero de un nmero natural es un nmero natural
- El factorial: n!, de un nmero natural (incluido el 0):
(1) si n = 0 entonces: 0! = 1
(2) si n > 0 entonces: n! = n (n-1)!
f_rec()

}
42
5.2 EJEMPLOS
Factorial.
En matemticas es frecuente defnir un concepto en funcin del proceso usado para generarlo.
Por ejemplo, una descripcin matemtica de n! es:
n! = 1 si n = 0
n(n-1) ....1 si n > 0
Tambin se puede defnir de forma ms sencilla:
n! = 1 si n = 0
n(n-1)! si n > 0
Esta segunda defnicin es recursiva, puesto que se expresa un mtodo factorial en trminos
de s mismo. A partir de esta defnicin, es fcil obtener un algoritmo recursivo para calcular el
factorial de un nmero natural n.
public static int factorialR(int n) {
int fact;
if (n==0)
fact=1;
else
fact=n*factorial(n-1);
return fact ;
}
43
De esto se pueden sacar varias conclusiones:
1. El mtodo se invoca a s mismo (esto es lo que lo convierte en recursivo).
2. Cada llamada recursiva se hace con un parmetro de menor valor que el de
la anterior llamada. As, cada vez se est invocando a otro problema idntico
pero de menor tamao.
3. Existe un caso degenerado en el que se acta de forma diferente, esto es,
ya no se utiliza la recursividad. Lo importante es que la forma en la que el
tamao del problema disminuye asegura que se llegar a este caso dege-
nerado o caso base. Esto es necesario, porque de lo contrario se estara
ejecutando indefnidamente.
44
Para determinar si un mtodo recursivo est bien diseado se utiliza el mtodo 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 ms pequeo Cada llamada recursiva se refere
a un caso ms pequeo del problema original?
3. La pregunta Caso-General. Suponiendo que la(s) llamada(s) recursi-
va(s) funciona(n) correctamente, as como el caso base, funciona co-
rrectamente todo el mtodo?
Si por ejemplo, le aplicamos esto al mtodo factorial(n), podemos comprobar cmo las res-
puestas a las 3 preguntas son afrmativas, con lo que podemos deducir que el algoritmo re-
cursivo est bien diseado.
Multiplicacin de conejos.
Supongamos que partimos de una pareja de conejos recin nacidos, y queremos calcular
cuntas 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 un n pequeo, por
ejemplo 6, la solucin se puede obtener fcilmente a mano:
45
Mes 1: 1 pareja, la inicial.
Mes 2: 1 pareja, ya que todava 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
Si deseamos saber el nmero de parejas al cabo de n meses, para un n cualquiera, podemos
construir un algoritmo recursivo fcilmente a partir de la siguiente relacin:
Parejas(n) = 1 si n <= 2 Parejas(n-1)+Parejas(n-2) si n > 2
En esta relacin Parejas(n-1) son las parejas vivas en el mes n-1, y Parejas(n-2) son las pare-
jas que nacen en el mes n a partir de las que haba en el mes n-2.
La serie de nmeros Parejas (1), Parejas (2), Parejas (3),... es conocida como la Serie de Fi-
bonacci, la cual modela muchos fenmenos naturales.
La funcin recursiva quedara:
public static int parejas(int n){
int par;
if (n<=2)
par=1;
else
par=parejas(n-1)+parejas(n-2);
return par;
}
46
Recursin indirecta.
A continuacin se expone un ejemplo de programa que utiliza recursin indirecta, y nos dice
si un nmero es par o impar. Al igual que el programa anterior, hay otro mtodo mucho ms
sencillo de determinar si un nmero es par o impar, basta con determinar el resto de la divisin
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 nmero impar.
47
Qu pasa si se hace una llamada recursiva que no termina?
Cada llamada recursiva almacena los parmetros que se pasaron al procedimiento, y otras
variables necesarias para el correcto funcionamiento del programa. Por tanto si se produce
una llamada recursiva infnita, esto es, que no termina nunca, llega un momento en el que no
quedar memoria para almacenar ms datos, y en ese momento se abortar la ejecucin del
programa. Para probar esto se puede intentar hacer esta llamada en el programa factorial de-
fnido anteriormente:
factorialR(-1);
Por supuesto no hay que pasar parmetros a una funcin que estn fuera de su dominio, pues
el factorialR est defnido solamente para nmeros naturales, pero es un ejemplo claro.
5.3 USO DE LA RECURSIVIDAD.
Se evidenci que la recursin es una tcnica potente de programacin para resolver, mediante
soluciones simples y claras, problemas de incluso gran complejidad.
Sin embargo, la recursin tambin tiene algunas desventajas desde el punto de vista de la
efciencia. Muchas veces un algoritmo iterativo es ms efciente que su correspondiente recur-
sivo. Existen dos factores que contribuyen a ello
a. La sobrecarga asociada con las llamadas a los mtodos.
Este factor es propio de los mtodos en general, aunque es mayor con
la recursividad, ya que una simple llamada inicial a un mtodo puede
generar un gran nmero de llamadas recursivas.
48
Aunque el uso de la recursin, como herramienta de modularidad, puede
clarifcar programas complejos que compensara la sobrecarga adicional, no
debemos usar la recursividad por el gusto de usarla. Por ejemplo, el mtodo
factorialR presentada antes no debera usarse en la prctica.
Se puede fcilmente escribir una funcin iterativa FactorialI tan clara como
la recursiva y sin la sobrecarga de sta.
public int factorialI(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 pre-
sentarn por lo general entre mtodos recursivos e iterativos.
El mtodo iterativo utiliza una construccin cclica (for, while,) para con-
trolar la ejecucin, mientras que la solucin recursiva utiliza una estructura
de seleccin.
La versin iterativa necesita un par de variables locales, mientras que la
versin recursiva slo una.
49
b. La inefciencia inherente de algunos mtodos recursivos.
Esta inefciencia es debida al mtodo empleado para resolver el problema concreto que se
est abordando.
Por ejemplo, en el mtodo presentado para resolver el problema de la multiplicacin de cone-
jos, apreciamos un gran inconveniente: los mismos valores son calculados varias veces. Por
ejemplo, para calcular ParejasR(7), tenemos que calcular ParejasR(3) cinco veces. Mientras
ms grande sea n, mayor inefciencia.
Se puede construir una solucin iterativa basada en la misma relacin, que acta hacia delante
en lugar de hacia atrs, y calcula cada valor slo una vez.
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;
}
50
El hecho de que la recursividad tenga estas dos desventajas, no quiere decir que sea una mala
tcnica 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.
Cundo utilizar la recursin?
No se debe utilizar cuando la solucin iterativa sea clara a simple vista. Sin embargo, en otros
casos, obtener una solucin iterativa es mucho ms complicado que una solucin recursiva,
y es entonces cuando se puede plantear la duda de si merece la pena transformar la solucin
recursiva en otra iterativa. En realidad la recursin se basa en almacenar en una pila los valo-
res 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 solucin recursiva en una iterativa, utilizando una pila, para cuando compile al cdigo de la
computadora.
Por otra parte, casi todos los esquemas de vuelta atrs y divide y vencers son recursivos,
pues de alguna manera parece mucho ms natural una solucin recursiva.
En general mucho ms sencillo escribir un programa recursivo que su equivalente iterativo.
51
5.4 EJERCICIOS
Ejercicio 1:
Escriba una defnicin recursiva de una funcin que tiene un parmetro n de tipo entero y que
devuelve el n-simo nmero de Fibonacci. Los nmeros de Fibonacci se defnen de la siguien-
te manera:
F
0
= 1
F
1
= 1
F
i
+
2
= F
i
+ F
i
+
1
Ejercicio 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 funcin factorial se defne como
n! = n *(n-1)*(n-2)**2*1
Descubra una versin recursiva de la frmula anterior y escriba una funcin recursiva que cal-
cule el valor de dicha frmula.
Ejercicio 3
Escriba una funcin recursiva que ordene de menor a mayor un arreglo de enteros basndose
en la siguiente idea: coloque el elemento ms pequeo en la primera ubicacin, y luego ordene
el resto del arreglo con una llamada recursiva.
52
Ejercicio 4
Escribir una funcin recursiva que devuelva la suma de los primeros N enteros
Ejercicio 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.
Ejercicio 6
Escribir un programa que calcule el mximo comn divisor (MCD) de dos enteros positivos. Si
M >= N una funcin 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
funcin 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 res-
ponsable de switchear los valores.
Ejercicio 7
Programe un mtodo recursivo que transforme un nmero entero positivo a notacin binaria.
Ejercicio 8
Programe un mtodo recursivo que transforme un nmero expresado en notacin binaria a un
nmero entero.
Ejercicio 9
Programe un mtodo recursivo que calcule la suma de un arreglo de nmeros enteros.
Ejercicio 10
Programe un mtodo recursivo que invierta los nmeros de un arreglo de enteros.
53
Ejercicio 11
Cul es el resultado de esta funcin para distintos valores de X?
Static int f(int x)
{
if (x >100)
{
return (x-10);
}
else
{
return(f(f(x+11)));
}
}
Ejercicio 12
Implemente una funcin recursiva que nos diga si una cadena es palndromo

También podría gustarte