Unidad 2 - Árboles

Anuncio
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
ARBOLES GENERALES
1. INTRODUCCIÓN.
Hasta ahora las estructuras de datos que hemos estudiado eran de tipo lineal, o
sea, existía una relación de anterior y siguiente entre los elementos que la
componían (cada elemento tendrá uno anterior y otro posterior, salvo los casos
de primero y último).Pues bien, aquí se va a estudiar una estructuración de los
datos más compleja: los árboles.
Este tipo de estructura es usual incluso fuera del campo de la informática. El
lector seguramente conoce casos como los árboles gramaticales para analizar
oraciones, los árboles genealógicos, representación de jerarquías, etc. La
estructuración en árbol de los elementos es fundamental dentro del campo de
la informática aplicándose en una amplia variedad de problemas como veremos
más adelante.
En principio podemos considerar la estructura de árbol de manera intuitiva
como una estructura jerárquica. Por tanto, para estructurar un conjunto de
elementos ei en árbol, deberemos escoger uno de ellos e1 al que llamaremos
raíz del árbol. Del resto de los elementos se selecciona un subconjunto e2,..., ek
estableciendo una relación padre-hijo entre la raíz y cada uno de dichos
elementos de manera que e1 es llamado el padre de e2, de e3,...ek y cada uno
de ellos es llamado un hijo de e1. Iterativamente podemos realizar la misma
operación para cada uno de estos elementos asignando a cada uno de ellos un
número de 0 o más hijos hasta que no tengamos más elementos que insertar.
El único elemento que no tiene padre es e1, la raíz del árbol. Por otro lado hay
un conjunto de elementos que no tienen hijos aunque sí padre que son
llamados hojas. Como hemos visto la relación de paternidad es una relación
uno a muchos.
Para tratar esta estructura cambiaremos la notación:
•
Las listas tienen posiciones. Los árboles tienen nodos.
•
Las listas tienen un elemento en cada posición. Los árboles tienen una
etiqueta en cada nodo (algunos autores distinguen entre árboles con y
sin etiquetas. Un árbol sin etiquetas tiene sentido aunque en la inmensa
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
mayoría de los problemas necesitaremos etiquetar los nodos. Es por ello
por lo que a partir de ahora sólo haremos referencia a árboles
etiquetados).
Usando esta notación, un árbol tiene uno y sólo un nodo raíz y uno o más
nodos hoja.
Desde un punto de vista formal la estructura de datos árbol es un caso
particular de grafo, más concretamente, en la teoría de grafos se denota de
forma similar como árbol dirigido. A pesar de ello, la definición formal más
usual de árbol en ciencias de la computación es la recursiva:
•
El caso básico es un árbol con un único nodo. Lógicamente este nodo es
a la vez raíz y hoja del árbol.
•
Para construir un nuevo árbol a partir de un nodo nr y k árboles A1,
A2,..., Ak de raíces n1, n2,..., nk con N1, N2,..., Nk elementos cada uno
establecemos una relación padre-hijo entre nr y cada una de las raíces
de los k árboles. El árbol resultante de N=1 + N1 +... + Nk nodos tiene
como raíz el nodo nr, los nodos n1, n2,...,nk son los hijos de nr y el
conjunto de nodos hoja está formado por la unión de los k conjuntos
hojas iniciales. Además a cada uno de los Ai se les denota subárboles de
la raíz.
Ejemplo: Consideremos el ejemplo de la siguiente figura.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Podemos observar que cada uno de los identificadores representa un nodo y la
relación padre-hijo se señala con una línea.Los árboles normalmente se
presentan en forma descendente y se interpretan de la siguiente forma:
•
E es la raíz del árbol.
•
S1,S2,S3 son los hijos de E.
•
S1,D1 componen un subárbol de la raíz.
•
D1,T1,T2,T3,D3,S3 son las hojas del árbol.
•
etc...
Además de los términos introducidos consideraremos la siguiente
terminología:
1. Grado de salida o simplemente grado.Se denomina grado de
un nodo al número de hijos que tiene.Así el grado de un nodo
hoja es cero.En la figura anterior el nodo con etiqueta E tiene
grado 3.
2. Caminos.Si n1,n2,...,nk es una sucesión de nodos en un árbol tal
que ni es el padre de ni+1 para 1<=i<=k-1 ,entonces esta
sucesión se llama un camino del nodo ni al nodo nk.La longitud de
un camino es el número de nodos menos uno, que haya en el
mismo.Existe un camino de longitud cero de cada nodo a sí
mismo.Ejemplos sobre la figura anterior:
E,S2,D2,T3 es un camino de E a T3 ya que E es padre de
S2,éste es padre de D2,etc.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
S1,E,S2 no es un camino de S1 a S2 ya que S1 no es padre
de E.
3. Ancestros y descendientes.Si existe un camino,del nodo a al
nodo b ,entonces a es un ancestro de b y b es un descendiente de
a.En el ejemplo anterior los ancestros de D2 son D2,S2 y E y sus
descendientes D2,T1,T2 y T3(cualquier nodo es a la vez ancestro
y descendiente de sí mismo). Un ancestro o descendiente de un
nodo,distinto de sí mismo,se llama un ancestro propio o
descendiente propio respectivamente.Podemos definir en términos
de ancestros y descendientes los conceptos de raíz,hoja y
subárbol:
En un árbol,la raíz es el único nodo que no tiene ancestros
propios.
Una hoja es un nodo sin descendientes propios.
Un subárbol de un árbol es un nodo,junto con todos sus
descendientes.
Algunos autores prescinden de las definiciones de ancestro propio
y descendiente propio asumiendo que un nodo no es ancestro ni
descendiente de sí mismo.
4. Altura.La altura de un nodo en un árbol es la longitud del mayor
de los caminos del nodo a cada hoja.La altura de un árbol es la
altura de la raíz.Ejemplo: en la figura anterior la altura de S2 es 2
y la del árbol es 3.
5. Profundidad.La profundidad de un nodo es la longitud del único
camino de la raíz a ese nodo.Ejemplo: en la figura anterior la
profundidad de S2 es 1.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
6. Niveles.Dado un árbol de altura h se definen los niveles 0...h de
manera que el nivel i está compuesto por todos los nodos de
profundidad i.
7. Orden de los nodos.Los hijos de un nodo usualmente están
ordenados de izquierda a derecha.Si deseamos explícitamente
ignorar el orden de los dos hijos, nos referiremos a un árbol como
un árbol no-ordenado.
La ordenación izquierda-derecha de hermanos puede ser
extendida para comparar cualesquiera dos nodos que no están
relacionados por la relación ancestro-descendiente.La regla a usar
es que si n1 y n2 son hermanos y n1 está a la izquierda de n2,
entonces todos los descendientes de n1 están a la izquierda de
todos los descendientes de n2.
RECORRIDOS DE UN ÁRBOL.
En una estructura lineal resulta trivial establecer un criterio de
movimiento por la misma para acceder a los elementos, pero en un árbol
esa tarea no resulta tan simple.No obstante, existen distintos métodos
útiles en que podemos sistemáticamente recorrer todos los nodos de un
árbol.Los tres recorridos más importantes se denominan
preorden,inorden y postorden aunque hay otros recorridos como es el
recorrido por niveles.
Si consideramos el esquema general de un árbol tal como muestra la
figura siguiente,los recorridos se definen como sigue:
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
8. El listado en preorden es:
Si el árbol tiene un único elemento, dicho elemento es el
listado en preorden.
Si el árbol tiene más de un elemento,es decir,una
estructura como muestra la figura 2,el listado en preorden
es listar el nodo raíz seguido del listado en preorden de
cada uno de los subárboles hijos de izquierda a derecha.
9. El listado en inorden es:
Si el árbol tiene un único elemento,dicho elemento es el
listado en inorden.
Si el árbol tiene una estructura como muestra la figura 2,el
listado en inorden es listar el subárbol A1 en inorden,y listar
el nodo raíz seguido del listado en inorden de cada uno de
los subárboles hijos de izquierda a derecha restantes.
10. El listado en postorden es:
Si el árbol tiene un único elemento,dicho elemento es el
listado en postorden.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Si el árbol tiene una estructura como muestra la figura 2,el
listado en postorden es listar en postorden cada uno de los
subárboles hijos de izquierda a derecha seguidos por el
nodo raíz.
11. El listado por niveles es: desde i=0 hasta la altura h del
árbol,listar de izquierda a derecha los elementos de profundidad
i.Como podemos observar,un nodo n1 aparece antes que n2 en el
listado por niveles si la profundidad de n1 es menor que la
profundidad de n2 usando el orden de los nodos definido
anteriormente para el caso en que tengan la misma profundidad.
Como ejemplo de listados veamos el resultado que se obtendría sobre el
árbol A de la figura 3.
Los resultados de los listados de preorden,postorden e inorden son los
siguientes:
12. Listado preorden.
A=Ar=rAvAs=rvAuAwAs= rvuAwAs=rvuwAxAyAzAs=
rvuwxAyAzAs=rvuwxyAzAs=rvuwxyzAs
=rvuwxyzsApAq=rvuwxyzspAq=rvuwxyzspq.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
13. Listado postorden.
A=Ar=AvAsr=AuAwvAsr= uAwvAsr=uAxAyAzwvAsr=
uxAyAzwvAsr=uxyAzwvAsr=uxyzwvAsr=
uxyzwvApAqsr=uxyzwvpAqsr=uxyzwvpqsr.
14. Listado inorden.
A=Ar=AvrAs=AuvAwrAs= uvAwrAs=uvAxwAyAzrAs=uvxw
AyAzrAs=uvxwyAzrAs=uvxwyzrAs=
uvxwyzrApsAq=uvxwyzrpsAq=uvxwyzrpsq.
Por último,el listado por niveles de este árbol es el
siguiente:r,v,s,u,w,p,q,x,y,z.
Finalmente es interesante conocer que un árbol no puede,en
general,recuperarse con uno solo de sus recorridos.Por ejemplo:Dada la
lista en inorden:vwyxzrtupsq,los árboles de la figura 4 tienen ese mismo
recorrido en inorden.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
2. UNA APLICACIÓN: ARBOLES DE
EXPRESIÓN.
Una importante aplicación de los árboles en la informática es la representación
de árboles sintácticos,es decir,árboles que contienen las derivaciones de una
gramática necesarias para obtener una determinada frase de un lenguaje.
Podemos etiquetar los nodos de un árbol con operandos y operadores de
manera que un árbol represente una expresión.Por ejemplo. en la figura 5 se
representa un árbol con la expresión aritmética (x-y)*(z/t).
Para que un árbol represente una expresión,hay que tener en cuenta que:
•
Cualquier hoja está etiquetada con uno y sólo un operando.
•
Cualquier nodo interior n está etiquetado por un operador.
En los árboles de expresión,la sucesión del preorden de etiquetas nos da lo
que se conoce como la forma prefijo de una expresión, en la que el operador
precede a su operando izquierdo y su operando derecho.En el ejemplo de la
figura 5,el preorden de etiquetas del árbol es *-xy/zt .
Análogamente,la sucesión postorden de las etiquetas de un árbol expresión
nos da lo que se conoce como la representación postfijo de una expresión.Así
en el ejemplo,la expresión postfijo del árbol es xy-zt/*.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Finalmente,el inorden de una expresión en un árbol de expresión da la
expresión infijo en sí misma,pero sin paréntesis.En el ejemplo,la sucesión
inorden del árbol anterior es x-y*z/t.
3. EL TIPO DE DATO ABSTRACTO "ARBOL".
La estructura de árbol puede ser tratada como un tipo de dato abstracto.A
continuación presentaremos varias operaciones sobre árboles y veremos como
los algoritmos de árboles pueden diseñarse en términos de estas operaciones.Al
igual que con otros TDA,existe una gran variedad de operaciones que pueden
llevarse a cabo sobre árboles.
Como podremos observar,cuando se construye una instancia de este tipo,tiene
al menos un elemento, es decir,hasta ahora no hemos hablado de la existencia
de un árbol vacío .Realmente, según la definición que vimos,efectivamente el
número mínimo de nodos de un árbol es 1.En las implementaciones usaremos
un valor especial ARBOL_VACIO para el caso en que el árbol no contenga
nodos,al igual que en listas existe el concepto de lista vacía.
De igual forma es necesario expresar en algunos casos que un nodo no existe
para lo cual también usaremos otro valor especial NODO_NULO.Un ejemplo de
su uso puede ser cuando intentemos extraer el nodo hijo a la izquierda de un
nodo hoja.
A continuación mostramos el conjunto de primitivas que nosotros
consideraremos:
1. CREAR_RAIZ(u).Construye un nuevo nodo r con etiqueta u y sin
hijos.Se devuelve el árbol con raíz r,es decir,un árbol con un único nodo.
2. DESTRUIR(T).Libera los recursos que mantienen el árbol T de forma
que para volver a usarlo se debe de asignar un nuevo valor con la
operación de creación.
3. PADRE(n,T).Esta función devuelve el padre del nodo n en el árbol T .Si
n es la raíz ,que no tiene padre,devuelve NODO_NULO(un valor que será
usado para indicar que hemos intentado salirnos del árbol).Como
precondición n no es NODO_NULO (por tanto T no es vacío).
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
.
4. HIJO_IZQDA(n,T).Devuelve el descendente más a la izquierda en el
siguiente nivel del nodo n en el árbol T, y devuelve NODO_NULO si n no
tiene hijo a la izquierda.Como precondición n no es NODO_NULO.
5. HERMANO_DRCHA(n,T).Devuelve el descendiente a la derecha del
nodo n en el árbol T ,definido para ser aquel nodo m con el mismo padre
que n ,es decir, padre p,de tal manera que m cae inmediatamente a la
derecha de n en la ordenación de los hijos de p (Por ejemplo,véase el
árbol de la figura 6). Devuelve NODO_NULO si n no tiene hermano a la
derecha.Como precondición n no es NODO_NULO.
6. ETIQUETA(n,T).Devuelve la etiqueta del nodo n en el árbol T
(manejaremos árboles etiquetados,sin embargo no es obligatorio definir
etiquetas para cada árbol).Como precondición n no es NODO_NULO.
7. REETIQUETA(e,n,T).Asigna una nueva etiqueta e al nodo n en el árbol
T.Como precondición n no es NODO_NULO.
8. RAIZ(T).Devuelve el nodo que está en la raíz del árbol T o NODO_NULO
si T es el árbol vacío.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
9. INSERTAR_HIJO_IZQDA(n,Ti,T).Inserta el árbol Ti como hijo a la
izquierda del nodo n que pertenece al árbol T.Como precondición n no es
NODO_NULO y Ti no es el árbol vacío.
10. INSERTAR_HERMANO_DRCHA(n,Td,T).Inserta el árbol Td como
hermano a la derecha del nodo n que pertenece al árbol T.Como
precondición n no es NODO_NULO y Td no es el árbol vacío.
11. PODAR_HIJO_IZQDA(n,T).Devuelve el subárbol con raíz hijo a la
izquierda de n del árbol T el cual se ve privado de estos nodos.Como
precondición n no es NODO_NULO.
12. PODAR_HERMANO_DRCHA(n,T).Devuelve el subárbol con raíz
hermano a la derecha de n del árbol T el cual se ve privado de estos
nodos.Como precondición n no es NODO_NULO.
A continuación veremos cómo implementar el TDA árbol y posteriormente
implementaremos los algoritmos de
recorrido:PREORDEN,POSTORDEN,INORDEN.
IMPLEMENTACIÓN DE ÁRBOLES.
UNA IMPLEMENTACIÓN MATRICIAL
Sea A un árbol en el cual los nodos se etiquetan 0,1,2,...,n-1,es decir,cada nodo
contiene un campo de información que contendrá estos valores.La
representación más simple de A que soporta la operación PADRE es una matriz
lineal P en la cual el valor de P[i] es un valor o un cursor al padre del nodo i.La
raíz de A puede distinguirse dándole un valor nulo o un valor a él mismo como
padre.Por ejemplo.,podemos usar un esquema de cursores donde P[i]=j si el
nodo j es el padre del nodo i,y P[i]=-1 (suponemos que NODO_NULO=-1) si el
nodo i es la raíz.La definición del tipo sería:
#define MAXNODOS 100
#define NODO_NULO -1
/*Por ejemplo*/
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
typedef int nodo;
typedef int *ARBOL;
/*Indica una casilla de la matriz*/
Esta representación usa la propiedad de los árboles de que cada nodo tiene un
único padre.Con esta representación el padre de un nodo puede encontrarse en
tiempo constante.Un camino hacia arriba en el árbol puede seguirse
atravesando el árbol en tiempo proporcional al número de nodos en el
camino.Podemos soportar también el operador ETIQUETA añadiendo otra
matriz L ,tal que L[i] es la etiqueta del nodo i ,o haciendo que los elementos de
la matriz A sean registros consistiendo en un entero(cursor)y una
etiqueta.EJEMPLO:Véase el árbol de la figura 7:
La representación de padre por cursores no facilita las operaciones que
requieren información de hijos.Dado un nodo n ,es costoso determinar los hijos
de n o la altura de n.Además,la representación por cursores del padre no
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
especifica el orden de los hijos de un nodo.Por tanto,operaciones como
HIJO_IZQDA y HERMANO_DRCHA no están bien definidas.Podríamos imponer
un orden artificial,por ejemplo,numerando los hijos de cada nodo después de
numerar el padre,y numerar los hijos en orden creciente de izquierda a
derecha.
Nota:Téngase en cuenta que aunque esta implementación no parece muy
adecuada, es posible ampliarla con la utilización de nuevos campos de
cursores.Por ejemplo:Podemos añadir dos matrices adicionales para almacenar
para cada nodo tanto el hijo a la izquierda como el hermano a la derecha.
IMPLEMENTACIÓN DE ÁRBOLES POR LISTAS DE HIJOS
Una forma útil e importante de representar árboles es formar para cada nodo
una lista de sus hijos.Las listas pueden representarse por cualquier
método,pero como el número de hijos que cada nodo puede tener puede ser
variable,las representaciones por listas enlazadas son las más apropiadas.La
figura 8 sugiere como puede representarse el árbol del ejemplo de la figura 7:
Hay una matriz de celdas de cabecera indexadas por nodos ,que suponemos
numerados 0,1,2,...,n-1. Cada punto de cabecera apunta a una lista enlazada
de elementos que son nodos.Los elementos sobre una lista encabezada por
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
cabecera[i] son los hijos de i(por ejemplo, 9 y 4 son los hijos de 8).Si
desarrollamos la estructura de datos que necesitamos en términos de un tipo
de dato abstracto tLista (de nodos) y damos una implementación particular de
listas,puede verse como las abstracciones encajan.
#include
/*Definidas apropiadamente*/
#define MAXNODOS 100
/*Por ejemplo*/
#define NODO_NULO -1
typedef int nodo;
typedef struct {
tLista cabecera[MAXNODOS];
tEtiqueta etiquetas[MAXNODOS];
nodo raiz;
}ARBOL;
Suponemos que la raíz de cada árbol está almacenada explícitamente en el
campo raíz.El -1 en el campo raíz se usa para representar el árbol nulo o
vacío.La siguiente función muestra el código para la operación HIJO_IZQDA:
nodo HIJO_IZQDA(nodo n,ARBOL T)
{
tLista L;
L=T.cabecera[n];
if(PRIMERO(L)==FIN(L))
return NODO_NULO;
/*No tiene hijos*/
else
return RECUPERA(PRIMERO(L),L); /*Recupera el primero(izqda)*/
}
Las demás operaciones son también fáciles de implementar utilizando la
anterior estructura para el tipo de dato y usando las primitivas del TDA Lista.
Nota:Las funciones PRIMERO,FIN y RECUPERA usadas en el ejemplo anterior
pertenecen al TDA Lista anteriormente estudiado.
IMPLEMENTACIÓN DE ÁRBOLES BASADA EN CELDAS
ENLAZADAS
Al igual que ocurre en los TDA estudiados (Listas,Pilas o Colas), un nodo puede
ser declarado de forma que la estructura del árbol pueda ir en aumento
mediante la obtención de memoria de forma dinámica,haciendo una petición de
memoria adicional cada vez que se quiere crear un nuevo nodo.
#define ARBOL_VACIO NULL
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
#define NODO_NULO NULL
typedef int tEtiqueta
/*Algún tipo adecuado*/
typedef struct tipocelda{
struct tipocelda *padre,*hizqda,*herdrchaAr;
tEtiqueta etiqueta;
}*nodo;
typedef nodo tArbol;
Observemos que bajo esta implementación cada nodo de un árbol contiene 3
punteros: padre que apunta al padre,hizqda que apunta al hijo izquierdo y
herdrcha que apunta al hermano a la derecha del nodo.Para esta
implementación de árbol vamos a presentar las funciones primitivas de las que
hablábamos al principio.Suponemos que para referenciar el nodo i la variable
puntero apuntará a ese nodo.Suponemos también unas variables de tipo nodo y
que la variable T de tipo árbol apunta a la raíz del árbol.
nodo PadreAr(nodo n,tArbol T)
{
return n->padre;
}
nodo HizqdaAr(nodo n,tArbol T)
{
return n->hizqda;
}
nodo HerdrchaAr(nodo n,tArbol T)
{
return n->herdrchaAr;
}
tEtiqueta EtiquetaAr(nodo n,tArbol T)
{
return n->etiqueta;
}
void ReEtiquetaAr(tEtiqueta e,nodo n,tArbol T)
{
n->etiqueta=e;
}
nodo RaizAr(tArbol T)
{
return T;
}
tArbol Crea0(tEtiqueta et)
{
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
tArbol raiz;
raiz=(tArbol)malloc (sizeof(struct tipocelda));
if (!raiz){
error("Memoria Insuficiente.");
}
raiz->padre=NULL;
raiz->hizqda=NULL;
raiz->etiqueta=et;
return raiz;
}
void Destruir(tArbol T)
{
if(T){
destruir(T->hizqda);
destruir(T->herdrcha);
free(T);
}
}
void Insertar_hijo_izqda(nodo n,tArbol Ti,tArbol T)
{
Ti->herdrcha=n->hizqda;
Ti->padre=n;
n->hizqda=Ti;
}
void Insertar_hermano_drcha(nodo n,tArbol Td,tArbol T)
{
if(n==raizAr(T)){
error("Memoria Insuficiente.");
}
Td->herdrcha=n->herdrcha;
Td->padre=n->padre;
n->herdrcha=Td;
}
tArbol Podar_hijo_izqda(nodo n,tArbol T)
{
tArbol Taux;
Taux=n->hizqda;
if(Taux!=ARBOL_VACIO){
n->hizqda=Taux->herdrcha;
Taux->padre=NODO_NULO;
Taux->herdrcha=NODO_NULO;
}
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
return Taux;
}
tArbol Podar_hermano_drcha(nodo n,tArbol T)
{
tArbol Taux;
Taux=n->herdrcha;
if(Taux!=ARBOL_VACIO){
n->herdrcha=Taux->herdrcha;
Taux->padre=NODO_NULO;
Taux->herdrcha=NODO_NULO;
}
return Taux;
}
Como vemos hemos implementado creaRaiz de manera que el árbol devuelto es
un único nodo.Es posible construir en C un procedimiento con un número
variable de parámetros:
•
El primero de los parámetros una etiqueta para el nodo raíz.
•
Los restantes parámetros de tipo tArbol que se insertarán como
subárboles(hijos) del nodo raíz.
Los podemos realizar mediante la implementación de un número de parámetros
indeterminado y haciendo uso del tipo va_list que podemos encontrar en el
fichero cabecera stdarg.h.El procedimiento podría ser el siguiente:
tArbol CreaRaiz(tEtiqueta et,tArbol T1,...,tArbol Tn,NULL)
{
va_list ap;
nodo n,aux,raiz;
/*Reservamos memoria para el nodo raiz*/
raiz=(nodo)malloc(sizeof(struct tipocelda));
if(!raiz){
error("Memoria Insuficiente.");
}
/*Inicializamos el nodo raiz*/
raiz->padre=NULL;
raiz->hizqda=NULL;
raiz->herdrcha=NULL;
raiz->etiqueta=et;
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
/*Un bucle para insertar los subarboles*/
va_start(ap,et);
/*Inicio de argumentos*/
for(;;){
n=(nodo)va_arg(ap,nodo);
if(n==NULL)break;
/*No quedan mas hijos*/
if(raiz->hizqda)aux->herdrcha=n;
else raiz->hizqda=n;
aux=n;
aux->herdrcha=NULL;
aux->padre=raiz;
}
va_end(ap);
/*Final de argumentos*/
return(tArbol)raiz;
}
La llamada a la función tendría como parámetros una etiqueta para el nodo raíz
del árbol resultante y una lista de nodos que podría ser vacía en cuyo caso el
árbol que resulta tiene un único nodo:su raíz con etiqueta et. Por
último,después de dicha lista,es necesario un parámetro adicional(NULL) que
indica el final de la lista tras cuya lectura el procedimiento dejaría de añadir
más hijos al nodo raíz que se está construyendo.
IMPLEMENTACIÓN DE LOS RECORRIDOS DE UN ÁRBOL
Recordemos que los recorridos de un árbol pueden ser de una forma directa en
Preorden, Inorden y Postorden.A continuación veremos la implementación de
estos tres recorridos. Así mismo,veremos un procedimiento de lectura de un
árbol en preorden.
PREORDEN
1. Visitar la raíz.
2. Recorrer el subárbol más a la izquierda en preorden.
3. Recorrer el subárbol de la derecha en preorden.
Vamos a escribir dos procedimientos uno recursivo y otro no recursivo que
toman un árbol y listan las etiquetas de sus nodos en preorden.Supongamos
que existen los tipos nodo y tArbol con etiquetas del tipo tEtiqueta definidos
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
anteriormente en la implementación por punteros.El siguiente procedimiento
muestra un procedimiento recursivo que , dado el nodo n,lista las etiquetas en
preorden del subárbol con raíz en n.
void PreordenArbol(nodo n,tArbol T)
{
Escribir(etiquetaAr(n,T));
for(n=hizqdaAr(n,T);n!=NODO_NULO;n=herdrchaAr(n,T))
PreordenArbol(n,T);
}
En esta función hemos supuesto que existe una rutina Escribir que tiene como
parámetro de entrada un valor de tipo tEtiqueta que se encarga de imprimir en
la salida estándar.Por ejemplo,si hemos realizado typedef int tEtiqueta la
función podría ser la siguiente:
void Escribir(tEtiqueta et)
{
fprintf(stdout,"%d",(int)et);
}
Por otro lado,en los programas C hemos usado el operador de desigualdad
entre un dato de tipo nodo y la constante ARBOL_VACIO.Para hacerlo más
independiente de la impementación sería conveniente programar una función
que podríamos llamar Arbol_Vacio que se añadiría como una nueva primitiva
que nos devuelve si el subárbol que cuelga del nodo es un árbol vacío.
Para el procedimiento no recursivo,usaremos una pila para encontrar el camino
alrededor del árbol.El tipo PILA es realmente pila de nodos,es decir,pila de
posiciones de nodos. La idea básica subyacente al algoritmo es que cuando
estamos en la posición p,la pila alojará el camino desde la raíz a p,con la raíz en
el fondo de la pila y el nodo p a la cabeza.El programa tiene dos modos de
operar.En el primer modo desciende por el camino más a la izquierda en el
árbol,escribiendo y apilando los nodos a lo largo del camino,hasta que
encuentra una hoja.A continuación el programa entra en el segundo modo de
operación en el cual vuelve hacia atrás por el camino apilado en la
pila,extrayendo los nodos de la pila hasta que se encuentra un nodo en el
camino con un hermano a la derecha.Entonces el programa vuelve al primer
modo de operación,comenzando el descenso desde el inexplorado hermano de
la derecha.El programa comienza en modo uno en la raíz y termina cuando la
pila está vacía.
void PreordenArbol(tArbol T)
{
pila P; /*Pila de posiciones:tElemento de la pila es el tipo nodo*/
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
nodo m;
P=CREAR(); /*Funcion de creacion del TDA PILA*/
m=raizAr(T);
do{
if(m!=NODO_NULO){
Escribir(etiquetaAr(n,T));
PUSH(m,P);
m=hizqdaAr(m,T);
}
else if(!VACIA(P)){
m=herdrchaAr(TOPE(P),T);
POP(P);
}
}while(!VACIA(P));
DESTRUIR(P);
/*Funcion del TDA PILA*/
}
INORDEN
1. Recorrer el subárbol más a la izquierda en inorden.
2. Visitar la raíz.
3. Recorrer el subárbol del siguiente hijo a la derecha en inorden.
Vamos a escribir un procedimiento recursivo para listar las etiquetas de sus
nodos en inorden.
void InordenArbol(nodo n,tArbol T)
{
nodo c;
c=hizqdaAr(n,T);
if(c!=NODO_NULO){
InordenArbol(c,T);
Escribir(etiquetaAr(n,T));
for(c=herdrchaAr(c,T);c!=NODO_NULO;c=herdrchaAr(c,T))
InordenArbol(c,T);
}
else Escribir(etiquetaAr(n,T));
}
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
POSTORDEN
1. Recorrer el subárbol más a la izquierda en postorden.
2. Recorrer el subárbol de la derecha en postorden.
3. Visitar la raíz.
El procedimiento recursivo para listar las etiquetas de sus nodos en postorden
es el siguiente:
void PostordenArbol(nodo n,tArbol T)
{
nodo c;
for(c=hizqdaAr(n,T);c!=NODO_NULO;c=herdrchaAr(c,T))
PostordenArbol(c,T);
Escribir(etiquetaAr(n,T));
}
LECTURA
A continuación veremos un procedimiento que nos realizará la lectura de los
nodos de un árbol introduciéndolos en preorden.La función implementada se
llama Lectura aunque se listan dos funciones(la rutina Lectura2 es una función
auxiliar que es usada por la primera).
void Lectura2(nodo n,tArbol T)
{
tEtiqueta etHijo,etHermano;
tArbol Hijo,Hermano;
fprintf(stdout,"Introduce hijo_izqda de: ");
Escribir(etiquetaAr(n,T));
Leer(&etHijo);
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
if(comparar(etHijo,FINAL)){
Hijo=creaRaiz(etHijo);
insertar_hijo_izqda(n,Hijo,T);
Lectura2(hizqdaAr(n,T),T);
}
fprintf(stdout,"Introduce her_drcha de: ");
Escribir(etiquetaAr(n,T));
Leer(&etHermano);
if(comparar(etHermano,FINAL)){
Hermano=creaRaiz(etHermano);
insertar_hermano_drcha(n,Hermano,T);
Lectura2(herdrchaAr(n,T),T);
}
}
tArbol Lectura()
{
tArbol T;
tEtiqueta et;
fprintf(stdout,"En caso de que no exista el hijo_izqdo o el"
"hermano_derecho introducir el valor: ");
Escribir(FINAL);
/*FINAL actua de centinela*/
fprintf(stdout,"\nIntroduce la raiz del arbol: ");
Leer(&et);
T=creaRaiz(et);
Lectura2(raizAr(T),T);
}
Es interesante observar 5 puntos en esta rutina:
•
Hemos supuesto que existe una función Leer que tiene como parámetro
de entrada un puntero a una zona de memoria que almacena un valor de
tipo tEtiqueta,y que sirve para leer de la entrada estándar un dato de
ese tipo y almacenarlo en dicha zona de memoria.
•
Existe una variable FINAL que contiene un valor para la etiqueta que "no
es legal" para indicar la inexistencia de un hijo a la izquierda y/o de un
hermano a la derecha.
•
Suponemos que existe una función comparar que tiene como parámetros
de entrada dos variables de tipo tEtiqueta y que devuelve un valor
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
entero distinto de 0 en caso de que las variables sean distintas según el
criterio implementado en la función.
•
Las sentencias insertar_hijo_izqda(...);Lectura2(...);no son
intercambiables,es decir,si hubieramos programado esas sentencias en
otro orden (Lectura2(...);insertar_hijo_izqda(...);) la función de lectura
no funcionaría correctamente.La comprobación de que esta afirmación es
correcta se deja como ejercicio al lector.
•
En la segunda sentencia if ocurre una situación similar al punto anterior.
•
Se puede completar la rutina de lectura para que prescinda de la lectura
de un posible hermano a la derecha de la raíz simplemente
preguntándonos si n es la raíz del árbol T.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
ARBOLES BINARIOS
1. INTRODUCCIÓN.
Un árbol binario puede definirse como un árbol que en cada nodo puede tener
como mucho grado 2,es decir,a lo más 2 hijos.Los hijos suelen denominarse
hijo a la izquierda e hijo a la derecha,estableciéndose de esta forma un orden
en el posicionamiento de los mismos.
Todas las definiciones básicas que se dieron para árboles generales
permanecen inalteradas sin más que hacer las particularizaciones
correspondientes.En los árboles binarios hay que tener en cuenta el orden
izqda-drcha de los hijos.Por ejemplo:los árboles binarios a) y b) de la figura
1(adoptamos el convenio de que los hijos a la izquierda son extraídos
extendiéndonos hacia la izquierda y los hijos a la derecha a la derecha) son
diferentes,puesto que difieren en el nodo 5.El árbol c por convenio se supone
igual al b) y no al a).
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
2. EL TIPO DE DATO ABSTRACTO ARBOL
BINARIO.
Para construir el TDA Arbol Binario bastaría con utilizar las primitivas de los
árboles generales pero dado la gran importancia y peculiaridades que tienen
este tipo de árboles, construiremos una serie de operaciones
específicas.Consideraremos las siguientes:
1. CREAR(e,Ti,Td).Devuelve un árbol cuya raíz contiene la etiqueta e
asignando como hijo a la izquierda Ti y como hijo a la derecha Td.
2. DESTRUIR(T).Libera los recursos que mantienen el árbol T de forma
que para volver a usarlo se debe asignar un nuevo valor con la operación
de creación.
3. PADRE(n,T).Esta función devuelve el padre del nodo n en el árbol T.En
caso de no existir,devuelve NODO_NULO.Como precondición n no es
NODO_NULO.
4. HIJO_IZQDA(n,T).Devuelve el hijo a la izquierda del nodo n en el
árbol T,y devuelve NODO_NULO si n no tiene hijo a la izquierda.Como
precondición, n no es NODO_NULO.
5. HIJO_DRCHA(n,T).Devuelve el hijo a la derecha del nodo n en el árbol
T,y devuelve NODO_NULO si n no tiene hijo a la derecha.Como
precondición, n no es NODO_NULO.
6. ETIQUETA(n,T).Devuelve la etiqueta del nodo n en el árbol T. Como
precondición, n no es NODO_NULO.
7. REETIQUETA(e,n,T).Asigna una nueva etiqueta e al nodo n en el árbol
T.Como precondición n no es NODO_NULO.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
8. RAIZ(T).Devuelve el nodo que está en la raíz del árbol T o
NODO_NULO si T es el árbol vacío.
9. INSERTAR_HIJO_IZQDA(n,Ti,T).Inserta el árbol Ti como hijo a la
izquierda del nodo n que pertenece al árbol T.En el caso de que existiese
ya el hijo a la izquierda,la primitiva se encarga de que sea destruído
junto con sus descendientes. Como precondiciones,Ti no es
ARBOL_VACIO y n no es NODO_NULO.
10. INSERTAR_HIJO_DRCHA(n,Td,T).Inserta el árbol Td como hijo a la
derecha del nodo n que pertenece al árbol T.En el caso de que existiese
ya el hijo a la derecha,la primitiva se encarga de que sea destruído junto
con sus descendientes.Como precondiciones,Td no es ARBOL_VACIO y n
no es NODO_NULO.
11. PODAR_HIJO_IZQDA(n,T).Devuelve el subárbol con raíz hijo a la
izquierda de n del árbol T el cual se ve privado de estos nodos.Como
precondición, n no es NODO_NULO.
12. PODAR_HIJO_DRCHA(n,T).Devuelve el subárbol con raíz hijo a la
derecha de n del árbol T el cual se ve privado de estos nodos.Como
precondición, n no es NODO_NULO.
3. IMPLEMENTACIÓN DEL TDA ARBOL
BINARIO Y DE LOS RECORRIDOS.
Vamos a realizar una implementación mediante punteros,para la cual hay que
realizar la siguiente declaración de tipos:
#define BINARIO_VACIO NULL
#define NODO_NULO NULL
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
typedef int tEtiqueta
/*Algun tipo adecuado*/
typedef struct tipoceldaB{
struct tipoceldaB *padre,*hizqda,*hdrcha;
tEtiqueta etiqueta;
}*nodoB;
typedef nodoB tArbolB;
Una posible implementación para las primitivas de árboles binarios es la
siguiente:
tArbolBin Crear0(tEtiqueta et)
{
tArbolBin raiz;
raiz = (tArbolBin)malloc(sizeof(struct tipoceldaBin));
if (raiz==NULL)
error(\"Memoria Insuficiente.\");
raiz->padre = NODO_NULO;
raiz->hizda = NODO_NULO;
raiz->hdcha = NODO_NULO;
raiz->etiqueta = et;
return(raiz);
}
tArbolBin Crear2(tEtiqueta et,tArbolBin ti,tArbolBin td)
{
tArbolBin raiz;
raiz=(tarbolBin)malloc(sizeof(struct tipoceldaBin));
if(!raiz){
error("Memoria Insuficiente.");
}
raiz->padre=NULL;
raiz->hizqda=ti;
raiz->hdrcha=td;
raiz->etiqueta=et;
if(ti!=NULL)
td->padre=raiz;
return raiz;
}
void Destruir(tArbolBin A)
{
if(A){
Destruir(A->hizqda);
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Destruir(A->hdrcha);
free(A);
}
}
nodoBin Padre(nodoBin n,tArbolBin A)
{
return(n->padre);
}
nodoBin Hderecha(nodoBin n,tArbolBin A)
{
return(n->hdrcha);
}
nodoBin Hizquierda(nodoBin n,tArbolBin A)
{
return(n->hizqda);
}
tEtiqueta Etiqueta(nodoBin n,tArbolBin A)
{
return(n->etiqueta);
}
void ReEtiqueta(tEtiqueta e,nodoBin n,tArbolBin A)
{
n->etiqueta=e;
}
nodoBin Raiz(tArbolBin A)
{
return A;
}
void InsertarHijoIzda(nodoBin n,tArbolBin ah,tArbolBin A)
{
Destruir(n->hizqda);
n->hizqda=ah;
ah->padre=n;
}
void InsertarHijoDrchaB(nodoBin n,tArbolBin ah,tArbolBin A)
{
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Destruir(n->hdrcha);
n->hdrcha=ah;
ah->padre=n;
}
tArbolBin PodarHijoIzqda(nodoBin n,tArbolBin A)
{
tArbolBin Aaux;
Aaux=n->hizqda;
n->hizqda=BINARIO_VACIO;
if(Aaux)
Aaux->padre=BINARIO_VACIO;
return Aaux;
}
tArbolBin PodarHijoDrcha(nodoBin n,tArbolBin A)
{
tArbolBin Aaux;
Aaux=n->hdrcha;
n->hdrcha=BINARIO_VACIO;
if(Aaux)
Aaux->padre=BINARIO_VACIO;
return Aaux;
}
Con las cuales podemos hacer la siguiente implementación de los recorridos en
preorden, postorden e inorden:
void PreordenArbol(nodoBin n,tArbolBin A)
{
if(n!=NODO_NULO){
Escribir(Etiqueta(n,A));
PreordenArbol(HizqdaB(n,A),A);
PreordenArbol(HdrchaB(n,A),A);
}
}
void PostordenArbol(nodoBin n,tArbolBin A)
{
if(n!=NODO_NULO){
PostordenArbol(Hizquierda(n,A),A);
PostordenArbol(Hderecha(n,A),A);
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Escribir(etiquetaB(n,A));
}
}
void InordenArbol(nodoBin n,tArbolBin A)
{
if(n!=NODO_NULO){
InordenArbol(Hizquierda(n,A),A);
Escribir(Etiqueta(n,A));
InordenArbol(HderechaB(n,A),A);
}
}
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
ARBOLES BINARIOS DE
BUSQUEDA
1. INTRODUCCIÓN.
La búsqueda en árboles binarios es un método de búsqueda simple, dinámico y
eficiente considerado como uno de los fundamentales en Ciencia de la
Computación. De toda la terminología sobre árboles,tan sólo recordar que la
propiedad que define un árbol binario es que cada nodo tiene a lo más un hijo
a la izquierda y uno a la derecha.Para construir los algoritmos consideraremos
que cada nodo contiene un registro con un valor clave a través del cual
efectuaremos las búsquedas.En las implementaciones que presentaremos sólo
se considerará en cada nodo del árbol un valor del tipo tElemento aunque en
un caso general ese tipo estará compuesto por dos:una clave indicando el
campo por el cual se realiza la ordenación y una información asociada a dicha
clave o visto de otra forma,una información que puede ser compuesta en la
cual existe definido un orden.
Un árbol binario de búsqueda(ABB) es un árbol binario con la propiedad de que
todos los elementos almacenados en el subárbol izquierdo de cualquier nodo x
son menores que el elemento almacenado en x ,y todos los elementos
almacenados en el subárbol derecho de x son mayores que el elemento
almacenado en x.
La figura 1 muestra dos ABB construidos en base al mismo conjunto de
enteros:
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Obsérvese la interesante propiedad de que si se listan los nodos del ABB en
inorden nos da la lista de nodos ordenada.Esta propiedad define un método de
ordenación similar al Quicksort,con el nodo raíz jugando un papel similar al del
elemento de partición del Quicksort aunque con los ABB hay un gasto extra de
memoria mayor debido a los punteros.La propiedad de ABB hace que sea muy
simple diseñar un procedimiento para realizar la búsqueda. Para determinar si k
está presente en el árbol la comparamos con la clave situada en la raíz, r.Si
coinciden la búsqueda finaliza con éxito, si k<r es evidente que k,de estar
presente,ha de ser un descendiente del hijo izquierdo de la raíz,y si es mayor
será un descendiente del hijo derecho.La función puede ser codificada
fácilmente de la siguiente forma:
#define ABB_VACIO NULL
#define TRUE 1
#define FALSE 0
typedef int tEtiqueta
/*Algun tipo adecuado*/
typedef struct tipoceldaABB{
struct tipoceldaABB *hizqda,*hdrcha;
tEtiqueta etiqueta;
}*nodoABB;
typedef nodoABB ABB;
ABB Crear(tEtiqueta et)
{
ABB raiz;
raiz = (ABB)malloc(sizeof(struct tceldaABB));
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
if (raiz == NULL)
error("Memoria Insuficiente.");
raiz->hizda = NODO_NULO;
raiz->hdcha = NODO_NULO;
raiz->etiqueta = et;
return(raiz);
}
int Pertenece(tElemento x,ABB t)
{
if(!t)
return FALSE
else if(t->etiqueta==x)
return TRUE;
else if(t->etiqueta>x)
return pertenece(x,t->hizqda);
else return pertenece(x,t->hdrcha);
}
Es conveniente hacer notar la diferencia entre este procedimiento y el de
búsqueda binaria.En éste podría pensarse en que se usa un árbol binario para
describir la secuencia de comparaciones hecha por una función de búsqueda
sobre el vector.En cambio en los ABB se construye una estructura de datos con
registros conectados por punteros y se usa esta estructura para la búsqueda.El
procedimiento de construcción de un ABB puede basarse en un procedimiento
de inserción que vaya añadiendo elementos al árbol. Tal procedimiento
comenzaría mirando si el árbol es vacío y de ser así se crearía un nuevo nodo
para el elemento insertado devolviendo como árbol resultado un puntero a ese
nodo.Si el árbol no está vacio se busca el elemento a insertar como lo hace el
procedimiento pertenece sólo que al encontrar un puntero NULL durante la
búsqueda,se reemplaza por un puntero a un nodo nuevo que contenga el
elemento a insertar.El código podría ser el siguiente:
void Inserta(tElemento x,ABB *t)
{
if(!(*t)){
*t=(nodoABB)malloc(sizeof(struct tipoceldaABB));
if(!(*t)){
error("Memoria Insuficiente.");
}
(*t)->etiqueta=x;
(*t)->hizqda=NULL;
(*t)->hdrcha=NULL;
} else if(x<(*t)->etiqueta)
inserta(x,&((*t)->hizqda));
else
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
inserta(x,&((*t)->hdrcha));
}
Por ejemplo supongamos que queremos construir un ABB a partir del conjunto
de enteros {10,5,14,7,12} aplicando reiteradamente el proceso de inserción.El
resultado es el que muestra la figura 2.
2. ANÁLISIS DE LA EFICIENCIA DE LAS
OPERACIONES.
Puede probarse que una búsqueda o una inserción en un ABB requiere O(log2n)
operaciones en el caso medio,en un árbol construido a partir de n claves
aleatorias, y en el peor caso una búsqueda en un ABB con n claves puede
implicar revisar las n claves,o sea,es O(n).
Es fácil ver que si un árbol binario con n nodos está completo (todos los nodos
no hojas tienen dos hijos) y así ningún camino tendrá más de 1+log2n
nodos.Por otro lado,las operaciones pertenece e inserta toman una cantidad de
tiempo constante en un nodo.Por tanto,en estos árboles, el camino que forman
desde la raíz,la secuencia de nodos que determinan la búsqueda o la inserción,
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
es de longitud O(log2n),y el tiempo total consumido para seguir el camino es
también O(log2n).
Sin embargo,al insertar n elementos en un orden aleatorio no es seguro que se
sitúen en forma de árbol binario completo.Por ejemplo,si sucede que el primer
elemento(de n situados en orden) insertado es el más pequeño el árbol
resultante será una cadena de n nodos donde cada nodo,excepto el más bajo
en el árbol,tendrá un hijo derecho pero no un hijo izquierdo.En este caso,es
fácil demostrar que como lleva i pasos insertar el i-ésimo elemento dicho
proceso de n inserciones necesita
O(n) pasos por operación.
pasos o equivalentemente
Es necesario pues determinar si el ABB promedio con n nodos se acerca en
estructura al árbol completo o a la cadena,es decir,si el tiempo medio por
operación es O(log2n),O(n) o una cantidad intermedia.Como es difícil saber la
verdadera frecuencia de inserciones sólo se puede analizar la longitud del
camino promedio de árboles "aleatorios" adoptando algunas suposiciones como
que los árboles se forman sólo a partir de inserciones y que todas las
magnitudes de los n elementos insertados tienen igual probabilidad.Con esas
suposiciones se puede calcular P(n),el número promedio de nodos del camino
que va de la raíz hacia algún nodo(no necesariamente una hoja).Se supone que
el árbol se formó con la inserción aleatoria de n nodos en un árbol que se
encontraba inicialmente vacío,es evidente que P(0)=0 y P(1)=1.Supongamos
que tenemos una lista de n>=2 elementos para insertar en un árbol vacío,el
primer elemento de la lista,x,es igual de probable que sea el primero,el
segundo o el n-ésimo en la lista ordenada.Consideremos que i elementos de la
lista son menores que x de modo que n-i-1 son mayores. Al construir el árbol,x
aparecerá en la raíz,los i elementos más pequeños serán descendientes
izquierdos de la raíz y los restantes n-i-1 serán descendientes
derechos.Esquemáticamente quedaría como muestra la figura 3.
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
Al tener tanto en un lado como en otro todos los elementos igual probabilidad
se espera que los subárboles izqdo y drcho de la raíz tengan longitudes de
camino medias P(i) y P(n-i-1) respectivamente.Como es posible acceder a esos
elementos desde la raíz del árbol completo es necesario agregar 1 al número de
nodos de cada camino de forma que para todo i entre 0 y n-1,P(n) puede
calcularse obteniendo el promedio de la suma:
El primer término es la longitud del camino promedio en el subárbol izquierdo
ponderando su tamaño.El segundo término es la cantidad análoga del subárbol
derecho y el término 1/n representa la contribución de la raíz.Al promediar la
suma anterior para todo i entre 1 y n se obtiene la recurrencia:
y con unas transformaciones simples podemos ponerla en la forma:
y el resto es demostrar por inducción sobre n que P(n)<=1+4log2n.
En consecuencia el tiempo promedio para seguir un camino de la raíz a un nodo
aleatorio de un ABB construido mediante inserciones aleatorias es O(log2n).Un
análisis más detallado demuestra que la constante 4 es en realidad una
constante cercana a 1.4.
De lo anterior podemos concluir que la prueba de pertenencia de una clave
aleatoria lleva un tiempo O(log2n).Un análisis similar muestra que si se incluyen
en la longitud del camino promedio sólo aquellos nodos que carecen de ambos
hijos o solo aquellos que no tienen hijo izqdo o drcho también la longitud es
O(log2n).
Terminaremos este apartado con algunos comentarios sobre los borrados en los
ABB.Es evidente que si el elemento a borrar está en una hoja bastaría
eliminarla,pero si el elemento está en un nodo interior,eliminándolo,podríamos
desconectar el árbol.Para evitar que esto suceda se sigue el siguiente
procedimiento:si el nodo a borrar u tiene sólo un hijo se sustituye u por ese hijo
Universidad de Mariano Gálvez de Guatemala.
Faculta de Ingeniería en Sistemas de Información.
Ingeniería en Sistemas de Información.
Jornada Diaria Vespertina.
Estructura de Datos.
Cat. Ing. J. Alvaro Díaz Ardavín.
y el ABB quedar&aacue; construido.Si u tiene dos hijos,se encuentra el menor
elemento de los descendientes del hijo derecho(o el mayor de los
descendientes del hijo izquierdo) y se coloca en lugar de u,de forma que se
continúe manteniendo la propiedad de ABB.
Descargar