You are on page 1of 19

Estructuras de datos no lineales Teora

Bloque 1: rboles
Podemos definir el concepto de arbol como una coleccin de elementos del mismo tipo, que, debemos
tener en cuenta que puede ser de cualquier tipo, tanto definido por el usuario como nativo del lenguaje a
utilizar.
Adems, estos elementos que pertenecen a un arbol determinado tienen la peculiaridad de que estn
relacionados entre s mediante una relacin de paternidad.
Definicin recursiva formal de rbol
Podemos dar una definicin recursiva algo ms formal de la estructura de datos rbol:
Dado un elemento n, ese unico elemento ya forma un arbol, formado por un nico nodo (su raz)
Adems, si existen varios elementos a los que llamaremos n1,n2..nk, raices de los subrboles A1,A2...Ak y
se establece una relacin de padre-hijo entre n y n1,n2...nk, nos queda el siguiente arbol:

n1

n2

nk

A1

A2

Ak

Conceptos sobre rboles:


Para profundizar en el estudio de esta peculiar estructura de datos, debemos comprender una serie de
conceptos que nos aclararn:

Grado de un nodo: Nmero de hijos del nodo en cuestin


Grado de un arbol: Numero mximo de grados de los nodos del arbol
Camino: Sucesin de nodos n1,n2...ni, donde se establece una relacin padre-hijo entre cada nodo
ni y cada nodo ni+1, adems, la longitud del camino es el numero de nodos que comprende 1
Rama: Camino que parte de un nodo y llega hasta una hoja
Altura: Longitud de la rama ms alta que parte de un nodo en concreto
Profundidad: Longitud del camino mas corto desde la raiz hasta el nodo en cuestin
Nivel: Coincide con la altura
Nodo raiz: Nodo sin ascendentes directos
Nodo hoja: Nodo sin descendientes directos
Desequilibrio: Diferencia entre la altura del subarbol izquierdo y derecho de un nodo determinado

rboles binarios
Como podemos suponer por su nombre, un arbol binario es un arbol de grado 2, es decir, cada nodo del
arbol podr tener 0, 1 2 hijos, pero nunca ms de 2.
Cuando pensamos en la forma de construir un arbol binario, quiza lo primero que se nos venga a la cabeza
si tenemos en mente la definicin recursiva de arbol binario es tener un constructor por defecto que nos
cree un arbol binario vaco, y adems un constructor parametrizado que reciba como parmetro un
elemento, un arbol (como subarbol derecho) y otro arbol como subarbol izquierdo, y que se encargue de
crear un arbol con el elemento como raiz, y los dos subarboles.
Los constructores seran, en este caso:
Abin();
Abin(const T& e, Abin<T>& Ai, Abin<T>& Ad);
Pero si nos ponemos a analizar la forma en la que se crea un arbol en este caso, encontramos con que el
arbol binario se creara desde las hojas hasta la raz, y esta quiz no es la forma ms adecuada de
construirlo, ya que la forma mas natural de construir un arbol es desde la raiz hasta las hojas.
Para hacerlo de esta forma, lo mas adecuado sera tener un constructor por defecto y tres metodos que
nos permitirian introducir una raiz, un hijo izquierdo y un hijo derecho.
Los metodos seran:
Abin();
void crearRaizB(const T& e);
void insertarHijoIzquierdoB(const T& e, nodo n);
void insertarHijoDerechoB(const T& e, nodo n);

Implementaciones ARBOLES BINARIOS (VECTORIAL)


La primera implementacin que hemos estudiado de un arbol binario es una implementacin esttica, en
la que se representa un arbol como un vector de celdas, siendo cada una de esta una estructura
formada por cuatro campos: Elemento, indice del hijo izquierdo, indice del hijo derecho e indice del
padre.
El motivo por el que se aade, adems, el indice del padre del nodo en cuestin es simple: Sin ella, la
operacin PadreB(nodo n) sera O(n), es decir, tendramos que recorrer todos los nodos del arbol para
encontrar al padre.
Por lo dems, la estructura es sencilla:
Elemento

Padre

HijoIzquierdo

HijoDerecho

Debemos tener en cuenta que esta estructura dinmica presenta algunas limitaciones, sobre todo en
cuanto al tamao del arbol en cuestin, que debe ser conocido a priori, ya que se determina en tiempo de
compilacin.
Operaciones : Insercin y eliminacin
Las operaciones de insercin y eliminacin en este caso no son demasiado complejas, pero vamos a tener
en cuenta el aspecto de la eficiencia en la operacin de eliminacion, ya que podriamos mejorarla.

La operacin de insercin de un hijo izquierdo, por ejemplo, sera de la siguiente forma:


template<typename T>
void insertarHijoizquierdoB(nodo n, const T& e){
assert(n>=0 && n<tamMax);
assert(nNodos<tamMax);
assert(nodos[n] != NODO_NULO);
assert(nodos[n].hizq == NODO_NULO);
nNodos++;
nodos[n].hizq = n;
nodos[nNodos].elto = e;
nodos[nNodos].padre = n;
nodos[nNodos].hizq = NODO_NULO;
nodos[nNodos].hder = NODO_NULO;
}
Pero con la operacin de eliminacin quiz tengamos ms que hablar, porque...Cmo sera una operacin
de eliminacin eficiente en este caso?
Podramos probar con tres variantes:
1. Borrar la celda en cuestin y reorganizar todo el vector
2. Marcar las celdas libres con un carcter especial
3. Machacar la posicin del indice con la posicin del ultimo y reorganizar solo a los parientes del
ultimo
Escogeremos la opcin tres, que nos ofrece una eficiencia mucho mayor que las anteriores.
La operacin de eliminacin, en este caso, de un hijo izquierdo, sera:
template<typename T>
void eliminarHijoIzquierdoB(nodo n){
assert(n>=0 && n<tamMax);
assert(nodos[n] != NODO_NULO);
typename Abin<T>::nodo hizqdo = nodos[n].hizq;
assert(hizqdo != NODO_NULO);
assert(nodos[hizqdo].hizq == NODO_NULO && nodos[hizqdo].hder == NODO_NULO);
if(hizqdo != nNodos-1){
nodos[hizqdo] = nodos[nNodos-1];
if(nodos[nodos[hizqdo].padre].hizq == nNodos 1){
nodos[nodos[hizqdo].padre].hizq = hizqdo;
else
nodos[nodos[hizqdo].padre].hder = hizqdo;
if(nodos[hizqdo].padre != NODO_NULO){
nodos[nodos[hizqdo].hizq].padre = hizqdo;
}
if(nodos[hizqdo].padre != NODO_NULO){
nodos[nodos[hizqdo].hder].padre = hizqdo;
}

Definicin de un arbol binario implementado mediante un vector de celdas:


template<typename T>
class Abin {
public:
typedef int nodo;
static const nodo NODO_NULO;
explicit Abin(size_t tamMax);
Abin(const Abin<T>& A);
Abin<T>& operator = (const Abin<T>& A);
void
void
void
void
void
void

crearRaizB(const T& e);


insertarHijoIzquierdoB(const T& e, nodo n);
insertarHijoDerecho(const T& e, nodo n);
eliminarRaizB();
eliminarHijoIzquierdoB(nodo n);
eliminarHijoDerechoB(nodo n);

bool arbolVacioB() const;


nodo raizB() const;
nodo padreB(nodo n) const;
nodo hijoIzquierdoB(nodo n) const;
nodo hijoDerechoB(nodo n) const;
private:
struct celda {
T elto;
nodo hizq,hder,padre;
celda(const T& e, nodo *p = 0): elto(e), padre(p), hizq(NODO_NULO), hder(NODO_NULO){}
};

};

int nNodos;
int tamMax;
celda* nodos;

Arboles binarios Implementaciones (ENLAZADA)


La implementacin enlazada nos permite realizar un arbol binario sin tener en cuenta el tamao de este
en la creacin, ya que podremos ir insertando y eliminando elementos sin preocuparnos por que alcance
un tamao maximo previamente fijado.
Adems, las operaciones son mucho ms sencillas de implementar y menos propensas a errores de
programacin.
Una cosa que debemos tener en cuenta en la implementacin mediante una estructura dinmica es: A
quien deben apuntar los punteros y cuantos deben haber por cada celda?
La primera idea que se nos viene a la mente es que, precisamente, deben ser dos punteros: uno al hijo
izquierdo del nodo en cuestin y otro al hijo derecho.
Pero nos ocurre tal y como nos ocurri en la implementacin anterior. De ser as, la operacin observadora
padreB(nodo n) sera de orden O(n).
Luego por cada celda almacenaremos: Elemento y punteros al padre, hizq y hder.

La definicin de la implementacin mediante celdas enlazadas de un arbol binario es la siguiente:


template<typename T>
class Abin {
public:

typedef celda* nodo;


static const nodo NODO_NULO;
explicit Abin(size_t tamMax);
Abin(const Abin<T>& A);
Abin<T>& operator = (const Abin<T>& A);
void
void
void
void
void
void

crearRaizB(const T& e);


insertarHijoIzquierdoB(const T& e, nodo n);
insertarHijoDerecho(const T& e, nodo n);
eliminarRaizB();
eliminarHijoIzquierdoB(nodo n);
eliminarHijoDerechoB(nodo n);

bool arbolVacioB() const;


nodo raizB() const;
nodo padreB(nodo n) const;
nodo hijoIzquierdoB(nodo n) const;
nodo hijoDerechoB(nodo n) const;
private:
struct celda {
T elto;
nodo hizq,hder,padre;
celda(const T& e, nodo *p = 0): elto(e), padre(p), hizq(NODO_NULO), hder(NODO_NULO){}
};
nodo r;
void destruirNodos(nodo n);
nodo copiar(nodo n);
};
En este caso es importante destacar la presencia de dos operaciones privadas: Destruirnodos eliminar un
nodo y todos sus descendientes, haciendo un recorrido en postorden, mientras que copiar copiar un nodo
y sus descendientes y devolver el nodo copiado, haciendo un recorrido en preorden.

Implementacin mediante un vector de posiciones relativas


La implementacin de un arbol binario mediante un vector de posiciones relativas es similar a la
implementacin vectorial de un arbol binario, con la peculiaridad de que en este vector slo se
almacenarn los elementos, teniendo que recurrir a sencillas formulas matematicas para conocer tanto el
hijo izquierdo, como el hijo derecho como el padre de un nodo.
La implementacin mediante un vector de posiciones relativas slo es rentable en cuanto a memoria
cuando tenemos un arbol completo o casi completo, ya que la ausencia de un nodo en un nivel h
provocar 2n+h-1 posiciones libres en el vector.
Hijo izquierdo: 2*n+1
Hijo derecho: 2*n+2
Padre: (n-1)/2
La cabecera (.h) de la implementacin mediante un vector de posiciones relativas ser la siguiente:
#ifndef ABIN_V
#define ABIN_V
#include <cassert>
template<typename T>
class Abin {
public:
typedef int nodo;
static const nodo NODO_NULO;
explicit Abin(size_t tam, const T& e_nulo = T());
Abin(const Abin<T>& A);
Abin<T>& operator = (const Abin<T>& A);
void crearRaizB(const T& e);
void insertarHijoIzquierdo(const T& e, nodo n);
void insertarHijoDerecho(const T& e, nodo n);
void eliminarRaiz();
void eliminarHijoizquierdo(nodo n);
void eliminarHijoDerecho(nodo n);
bool arbolVacioB() const;
nodo raizB() const;
nodo padreB(nodo n) const;
nodo hijoIzquierdo(nodo n) const;
nodo hijoDerecho(nodo n) const;
private:
T* nodos;
T elto_nulo;
int tamMax;
};

Recorrido de arboles binarios:


Por lo general, podemos realizar el recorrido de una estructura de arbol de dos formas distintas:

En profundidad
En anchura

Dentro de los recorridos en profundidad tenemos:

Recorrido en preorden
Recorrido en inorden
Recorrido en postorden

Recorrido en preorden
Cuando se realiza el recorrido en preorden de un arbol binario, el nodo que primero se visita es el nodo
raz, luego el subarbol izquierdo y luego el subarbol derecho de ste.
Por ejemplo, en el siguiente arbol:

El recorrido en preorden sera:


2 7 2 6 5 11 5 9 4
Una posible implementacin recursiva del recorrido en preorden sera:
void preordenAbin(Abin<T>& A, nodo n) {
if(n != NODO_NULO){
procesar(n,A);
preordenAbin(A,A.hijoIzquierdoB(n));
preordenAbin(A,A.hijoDerechoB(n));
}

Recorrido en inorden
En el recorrido en inorden, primero se visita el subarbol izquierdo del arbol en cuestin, despus se visita
la raz y por ultimo el subrbol derecho, aunque el orden de izq-der puede variar.
Por ejemplo, en el arbol siguiente:

El recorrido en inorden sera:


2 7 5 6 11 2 5 9 4
Una posible implementacin recursiva para el recorrido en inorden del arbol sera:
inordenAbin(Abin<T>& A, nodo n){
if(n != NODO_NULO){

inordenAbin(A,A.hijoIzquierdoB(n));
procesar(n,A);
inordenAbin(A,A.hijoDerechoB(n));

Recorrido en postorden
En el recorrido en postorden, primero se visita el subarbol izquierdo del arbol en cuestin, despus se
visita el subrbol derecho, y por ultimo se visita la raz. Aunque el orden de izq-der puede variar.
Por ejemplo, en el arbol siguiente:

El recorrido en inorden sera:


2 5 11 6 7 5 9 4 - 2
Una posible implementacin recursiva para el recorrido en inorden del arbol sera:
postordenAbin(Abin<T>& A, nodo n){
if(n != NODO_NULO){

postordenAbin(A,A.hijoIzquierdoB(n));
postordenAbin(A,A.hijoDerechoB(n));
procesar(n,A);

Implementacin iterativa del recorrido en preorden


Tambin podemos hacer una implementacin iterativa del recorrido en preorden de un arbol binario dado.
En la implementacin iterativa usaremos una pila para almacenar los nodos que quedan por procesar.
preordenAbin(Abin<T>& A, nodo n) {
Pila<nodo> P;
P.push(n);

while(!P.vacia()){
n = P.tope(); P.pop();
procesar(n,A);
if(n != NODO_NULO){
P.push(A.hijoIzquierdoB(n));
P.push(A.hijoIzquierdoB(n));
}

Recorrido en anchura
El recorrido en anchura, o por niveles, se basa en ir visitando los nodos del abrol en cuestin segn el
nivel en el que estn situados.
Por ejemplo, en el arbol binario siguiente:

Recorrido en inorden
En el recorrido en inorden, primero se visita el subarbol izquierdo del arbol en cuestin, despus se visita
la raz y por ultimo el subrbol derecho, aunque el orden de izq-der puede variar.
Por ejemplo, en el arbol siguiente:

El recorrido en anchura sera:


2 7 5 2 6 9 5 11 - 4
Una posible implementacin iterativa para el recorrido en anchura sera:
Cola<nodo> C;
C.push(n);
while(!C.vacia()){
n = C.frente(); C.pop();
procesar(n,A);
if(n != NODO_NULO){
C.push(A.hijoIzquierdoB(n));
C.push(A.hijoDerechoB(n));
}
}

rboles generales
Podemos definir el concepto de rboles generales como rboles cuyos nodos tienen un grado indefinido, es
decir, cada nodo de un arbol general puede tener un numero indeterminado de hijos.
Un ejemplo de un arbol general es el siguiente:

Por convencin, denotaremos a los elementos del arbol general de la siguiente forma:

Nodo raz: Un nodo sin ascendentes directos (Sin padres, ni abuelos)


Nodo hoja: Nodo sin descendientes directos
Hijo izquierdo: Primer hijo, empezando por la derecha, de un nodo determinado.
Hermano derecho: Hijo de un nodo, tambin, pero que es el siguiente a la derecha del nodo.

Existen multitud de implementaciones posibles a la hora de representar un arbol general, pero nosotros
estudiaremos dos de ellas:

La representacin esttica, mediante un vector de celdas, que explicaremos con ms detalle un


poco ms adelante en estos apuntes
La representacin dinmica, mediante celdas enlazadas.

Construccin de un arbol general


Al igual que nos encontramos con el dilema de cmo construir un arbol binario, en la primera parte de
estos apuntes, cuando llegamos a un arbol general (Que no es ms que una generalizacin del concepto de
arbol binario) nos encontramos con el mismo dilema: Cmo construir un arbol general?
Como vimos que descartabamos la opcin de crear un arbol desde la hojas hasta la raiz, ya que esta no
era la manera natural de hacerlo, vamos sobre una base slida de que la construccin del arbl debe ser
desde la raz hasta las hojas.
Por tanto, escogeremos cuatro operaciones para realizar la construccin de un arbol general:

Agen(); El constructor por defecto, que crea un arbol vaco.


Void crearRaiz(const T& e); Crea una raz
Void insertarHijoIzqdo(const T& e, nodo n); Inserta un nuevo hijo izquierdo al nodo n.
Void insertarHermDrcho(const T& e, nodo n); Inserta un nuevo hermano derecho al nodo n.

Operaciones:
Vamos a pasar a describir la especificacin de un arbol general, as como las precondiciones y
postcondiciones que deben de cumplirse en stas.
La clase rbol general o Agen<T> en su versin genrica, estar formada por los siguientes mtodos:
Agen()

Crear un arbol vaco

Void crearRaiz(const T& e)

Pre: El arbol est vaco


Post: Inserta una raz con el elemento e

Void insertarHijoIzqdo(const T& e, nodo n)

Pre: Existe n
Post: Inserta un nuevo hijo izquierdo de n. Si existe
previamente el hijo izquierdo, este pasa a ser el
hermano derecho del nuevo nodo.

Void insertarHermDrcho(const T& e, nodo n)

Pre: Existe n, y n no es raz


Post: Inserta un nuevo hermano derecho de n, si
existe previamente, pasa a ser el hermano derecho
del nuevo nodo.

Void eliminarRaiz()

Pre: La raiz es una hoja


Post: Elimina la raiz

Void eliminarHijoIzquierdo(nodo n)

Pre: Existe n, existe el hijo izquierdo de n y adems


este es una hoja.
Post: Elimina el hijo izquierdo de n, si existe algn
otro hijo de n, pasar a ser el hijo izquierdo.

Void eliminarHermDrcho(nodo n)

Pre: Existe n, n no es la raz, existe el hermano


derecho de n y adems ste es una hoja.
Post: Elimina el hermano derecho de n, si existe
algun otro hermano de n, pasar a ser el hermano
derecho.

Bool arbolVacio() const

Pre: Ninguna
Post: Devuelve true si el arbol est vaco, false en
otro caso.

Nodo raiz() const

Devuelve el nodo raz

Nodo padre(nodo n) const

Pre: N existe
Post: Si n es raz devuelve NODO_NULO, si no,
devuelve el padre de n.

Nodo hijoIzquierdo(nodo n) const

Pre: N existe
Post: Si existe el hijo izquierdo del nodo devuelve
el hijo izquierdo, si no, devuelve nodo nulo

Nodo HermDrcho(nodo n) const

Pre: N existe
Post: Si existe el hermano derecho del nodo lo
devuelve, si no existe o n es la raiz devuelve un
nodo nulo.

Implementacin mediante un vector de celdas de un arbol general


La implementacion mediante un vector de celdas de un arbol general es una implementacin esttica, es
decir, su tamao debe ser pasado como parmetro al constructor (que adems debe ser explcito) y,
adems, no puede ser cambiado.
Se denomina, por tanto, en tiempo de compilacin.
El problema que plantean este tipo de estructuras frente a las estructuras dinmicas es simple: No
podemos redimensionar el vector, tiene un tamao mximo.
Lo que haremos en esta ocasin ser utilizar un vector de celdas, cada una de ellas compuesta de un
elemento, el ndice del padre y una lista de hijos.
Elto

...

Padre

NULO (-1) ...

Hijos

(1,2,3)

...

Pero... Cmo hacer las operaciones para que esta implementacin sea eficiente y podamos utilizarla?
Las operaciones, a excepcin de la insercin y eliminacin de nodos dentro del arbol, no tendrn ms
complicacin que el manejo e los indices, pero la insercin y eliminacin son casos especiales de sta:
Operaciones

Insercin: En la operacin de insercin de un nodo determinado (Por ejemplo, un hijo izquierdo),


debemos asegurarnos, por lo general, de cuatro cosas: El nodo existe (es 0(raz) o su padre es
distinto del nodo nulo), caben mas elementos en el vector, y el numero de nodos es > 0. La
insercin es sencilla: Vamos recorriendo los elementos del vector hasta encontrar una posicin
libre, ah insertamos elto = e, y padre = n, y insertamos en la primera posicin de la lista de hijos
del padre (n) el indice de la posicin encontrada. Para el caso de insercin de hermano derecho es
similar, y nos tenemos que ocupar de buscar una posicin libre, insertar elto y padre, y adems
insertar en la lista DE HIJOS DEL PADRE DE N el indice en la posicin SIGUIENTE a n.

Eliminacin: La eliminacin es algo ms tediosa, se basa en comprobar que el nodo existe, que,
dada su lista de hijos sta no est vaca, que el primer elemento de su lista de hijos (hizq) tiene
una lista de hijos vaca, poner ese hizq.padre a nodo_nulo y eliminar de la lista de hijos de n la
primera posicin. Para eliminar en el hijo derecho es ms tedioso an: Debemos encontrar un
nodo p, siguiente del nodo n en la lista de hijos del padre de n, adems este nodo p no
puede ser igual a fin(), y hederecho ser lhp.elemento(p). Debemos comprobar tambin que la
lista de hijos de hederecho est vaca, y ya entonces podemos colocar su padre a nodo nulo y
eliminar p de la lista de hijos del padre.
Especificacin (Fichero de cabecera .h)
#ifndef AGEN_V
#define AGEN_V
#include "Lista.h"
#include <cassert>
template<typename T>
class Agen {
struct celda;
public:

typedef int nodo;


static const nodo NODO_NULO;
explicit Agen(size_t tamMax);
Agen(const Agen<T>& A);
Agen<T>& operator = (const Agen<T>& A);
void crearRaiz(const T& e);
void insertarHijoIzqdo(const T& e, nodo n);
void insertarHermDrcho(const T& e, nodo n);
void eliminarRaiz();
void eliminarHijoizqdo(nodo n);
void eliminarHermDrcho(nodo n);
bool arbolVacio() const;
nodo raiz() const;
nodo padre(nodo n) const;
nodo hijoIzqdo(nodo n) const;
nodo hermDrcho(nodo n) const;
private:
struct celda{
T elto;
nodo padre;
Lista<nodo> hijos;
};
celda* nodos;
int maxTam;
int nNodos;
};
Arboles generales Implementacin mediante celdas enlazadas
Otra de las posibles implementaciones que podemos hacer de la estructura arbol general es mediante
celdas enlazadas, siendo esta implementacin una implementacin dinmica, es decir, que no tiene por
qu tener un tamao determinado en tiempo de compilacin, sino que se va redimensionando conforme
aadimos o eliminamos nodos al arbol.
Esto es util a la hora de realizar algunos problemas en los que no sabemos, a ciencia cierta, el tamao del
arbol en cuestin, o a la hora de implementar un arbol general con mayor comodidad, ya que este tipo de
estructura es ms sencilla de implementar que las anteriores.
La representacin es sencilla: Cada nodo est formado por un tipo de datos celda, que almacena,
adems de su elemento correspondiente, un puntero tanto a su padre, como a su hijo izquierdo, como a
su hermano derecho, si lo tiene.
Tambin tendremos como peculiaridad que usaremos nodo r, es decir, el nodo raz que apuntar al
primer elemento.
La implementacin la realizaremos a continuacin, pero antes vamos a destacar un par de funciones que
nos pueden resultar algo confusas:

destruirNodos(nodo n) Eliminar un nodo y TODOS sus descendientes.


copiar(nodo n) Copiar un nodo y sus descendientes. Devolver el nodo copiado.

Implementacin de la cabecera:
#ifndef AGEN_H
#define AGEN_H
#include <cassert>
template<typename T>
class Agen {
struct celda; // Declaracion adelantada
private:
typedef celda* nodo;
static const nodo NODO_NULO;
Agen();
Agen(const Agen<T>& A);
Agen<T>& operator = (const Agen<T>& A);
void crearRaiz(const T& e);
void insertarHijoIzqdo(const T& e, nodo n);
void insertarHermDrcho(const T& e, nodo n);
void eliminarRaiz();
void eliminarHijoIzqdo(nodo n);
void eliminarHermDrcho(nodo n);
bool arbolVacio();
nodo raiz() const;
nodo padre(nodo n) const;
nodo hijoIzqdo(nodo n) const;
nodo hermDrcho(nodo n) const;
private:
struct celda{
T elto;
nodo* padre,hizq,hder;
celda(const T& e, nodo* p=0): elto(e),padre(p),hizq(NODO_NULO),hder(NODO_NULO){}
};
nodo r;

};

void destruirNodos(nodo n);


nodo copiar(nodo n);

rboles binarios de bsqueda


Un arbol binario de bsqueda no es ms que un arbol binario de grado 2 (Como recordaremos, un arbol de
este tipo puede tener 0, 1 o 2 hijos), teniendo en cuenta que, entre sus elementos se cumple una relacin
de orden muy particular: Dado un nodo cualquiera del arbol, los elementos situados en el subarbol
izquierdo de ste sern menores que el nodo en cuestin, y los elementos situados a la derecha de ste
sern mayores que el nodo en cuestin.
Adems, debemos tener en cuenta que los arboles binarios de busqueda no admiten elementos repetidos,
ya que podria falsear los resultados a la hora de buscar un elemento en concreto.

Un arbol binario de busqueda, puede ser, por ejemplo, el siguiente:

Un arbol binario de busqueda bien equilibrado, como por ejemplo el de la imagen anterior, nos permitir
realizar bsqueda en O(log2(n)), lo que, por ejemplo, para el caso anterior, seran 3 accesos para 9 nodos.
Sin embargo, y esto es algo a tener muy en cuenta a la hora de insertar elementos en un arbol binario (El
orden en el que se inserten) tambin, podemos tener un arbol binario similar a una lista si insertsemos
los elementos de la siguiente forma:

14
13
10
8
7
6
4
3
1

// En este caso, buscar elemento = O(n).

Operaciones del Abb


Las operaciones ms importantes en un arbol binario de bsqueda son, por lo general:

Insercion de un elemento
Eliminacin de un elemento
Buscar un elemento

Insercin de un elemento
Para insertar un elemento en un arbol de bsqueda, la operacin insertar recibir como parmetro el
elemento en cuestin, y el algoritmo ir haciendo sucesivas llamadas recursivas diferenciando dos casos:
El caso en el que el elemento es mayor que la la raiz del arbol en cuestin, y el caso en el que el
elemento es menor.
La funcin para hacerlo ser la siguiente:
template<typename T>
void Abb<T>::insertar(const T& e){
if(e < r -> elto){
Abb<T> Ai = r -> hizq;
Ai.insertar(e);
r -> hizq = Ai.r;
Ai.r = NODO_NULO;
}
if(e > r -> elto){
Abb<T> Ad = r -> hder;
Ad.insertar(e);
r -> hder = Ad.r;
Ad.r = NODO_NULO;
}
}
Eliminacin de un elemento
En el caso de la eliminacin de un elemento pasado como parmetro, primero debemos recurrir a buscar
el elemento en el propio arbol binario, lo que supone una operacin similar a la anterior.
En este caso, distinguiremos entre tres casos:

El elemento no tiene hijos: Con lo cual se elimina simplemente.


El elemento tiene slo un hijo (Izquierdo o derecho): Toma el valor de ste y se elimina el hijo
El elemento tiene dos hijos: Se elimina el hijo mas pequeo de entre los grandes, y se toma el
valor de ese para la raiz

La funcin ser, por tanto:


template<typename T>
void Abb<T>::eliminar(const T& e){
if(e < r -> elto){
Abb<T> Ai = r -> hizq;
Ai.eliminar(e);
r -> hizq = Ai.r;
Ai.r = NODO_NULO;
}
else if(e < r -> elto){
Abb<T> Ai = r -> hizq;

Ai.eliminar(e);
r -> hizq = Ai.r;
Ai.r = NODO_NULO;
}
else {
if(r != NODO_NULO && r -> hizq == NODO_NULO && r -> hder == NODO_NULO){
delete(r);
r = NODO_NULO;
}
else if(r -> hizq != NODO_NULO && r -> hder == NODO_NULO || r -> hizq == NODO_NULO && r ->
hder != NODO_NULO){
if(r -> hizq != NODO_NULO){
r -> elto = r -> hizq -> elto;
delete r -> hizq;
r -> hizq = NODO_NULO;
}
else {
r -> elto = r -> hder -> elto;
delete r -> hder;
r -> hder = NODO_NULO;
}
}
else {

}
}

r -> elto = borrarMin(r -> hder);

Implementacin de un arbol binario de bsqueda:


Si tenemos en cuenta las operaciones anteriormente mostradas de un arbol binario, la implementacin de
stas es simple:
La cabecera de un arbol binario es la siguiente:
#ifndef ABB_H
#define ABB_H
#include <cassert>
template<typename T>
class Abb {
struct celda;
public:
typedef celda* nodo;
static const nodo NODO_NULO;
Abb(nodo n = NODO_NULO);
Abb(const Abb<T>& A);
Abb<T>& operator = (const Abb<T>& A);
void insertar(const T& e);
void eliminar(const T& e);
nodo buscar(const T& e);
bool vacio() const;

private:
struct celda{
T elto;
nodo* padre,hizq,hder;
celda(const T& e, nodo* p = 0): elto(e), p(padre){}
};
nodo r;

};

T borrarMin(nodo n);
nodo copiar(nodo n);

Y las operaciones borrarMin y copiar son:


template<typename T>
T Abb<T>::borrarMin(nodo n){
if(n -> hizq == NODO_NULO){
T elemento = n -> hizq -> elto;
delete n -> hizq;
n -> hizq = NODO_NULO;
return e;
}
else {
return borrarMin(n -> hizq);
}
}
template<typename T>
nodo Abb<T>::copiar(nodo n){
typename Abb<T>::nodo m = NODO_NULO;
if(n != NODO_NULO){
m = new celda(n -> elto);
if(n -> hizq != NODO_NULO){
m -> hizq = copiar(n -> hizq);
m -> hizq -> padre = m;
}
if(n -> hder != NODO_NULO){
m -> hder = copiar(n -> hder);
m -> hder -> padre = m;
}
}
return m;
}

You might also like