Professional Documents
Culture Documents
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
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);
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.
};
int nNodos;
int tamMax;
celda* nodos;
En profundidad
En anchura
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:
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:
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:
postordenAbin(A,A.hijoIzquierdoB(n));
postordenAbin(A,A.hijoDerechoB(n));
procesar(n,A);
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:
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:
Existen multitud de implementaciones posibles a la hora de representar un arbol general, pero nosotros
estudiaremos dos de ellas:
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()
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 eliminarRaiz()
Void eliminarHijoIzquierdo(nodo n)
Void eliminarHermDrcho(nodo n)
Pre: Ninguna
Post: Devuelve true si el arbol est vaco, false en
otro caso.
Pre: N existe
Post: Si n es raz devuelve NODO_NULO, si no,
devuelve el padre de n.
Pre: N existe
Post: Si existe el hijo izquierdo del nodo devuelve
el hijo izquierdo, si no, devuelve nodo nulo
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.
...
Padre
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
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:
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;
};
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
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:
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 {
}
}
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);